NotificationStackScrollLayout.java revision a5eaa6034dd48fab0f5a232c09ebed35f359963e
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;
21
22import android.graphics.Canvas;
23import android.graphics.Paint;
24
25import android.util.AttributeSet;
26import android.util.Log;
27
28import android.view.MotionEvent;
29import android.view.VelocityTracker;
30import android.view.View;
31import android.view.ViewConfiguration;
32import android.view.ViewGroup;
33import android.view.ViewTreeObserver;
34import android.view.animation.AnimationUtils;
35import android.widget.OverScroller;
36
37import com.android.systemui.ExpandHelper;
38import com.android.systemui.R;
39import com.android.systemui.SwipeHelper;
40import com.android.systemui.statusbar.ExpandableNotificationRow;
41import com.android.systemui.statusbar.ExpandableView;
42import com.android.systemui.statusbar.stack.StackScrollState.ViewState;
43import com.android.systemui.statusbar.policy.ScrollAdapter;
44
45import java.util.ArrayList;
46
47/**
48 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
49 */
50public class NotificationStackScrollLayout extends ViewGroup
51        implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
52        ExpandableView.OnHeightChangedListener {
53
54    private static final String TAG = "NotificationStackScrollLayout";
55    private static final boolean DEBUG = false;
56
57    /**
58     * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
59     */
60    private static final int INVALID_POINTER = -1;
61
62    private SwipeHelper mSwipeHelper;
63    private boolean mSwipingInProgress;
64    private int mCurrentStackHeight = Integer.MAX_VALUE;
65    private int mOwnScrollY;
66    private int mMaxLayoutHeight;
67
68    private VelocityTracker mVelocityTracker;
69    private OverScroller mScroller;
70    private int mTouchSlop;
71    private int mMinimumVelocity;
72    private int mMaximumVelocity;
73    private int mOverscrollDistance;
74    private int mOverflingDistance;
75    private boolean mIsBeingDragged;
76    private int mLastMotionY;
77    private int mActivePointerId;
78
79    private int mSidePaddings;
80    private Paint mDebugPaint;
81    private int mContentHeight;
82    private int mCollapsedSize;
83    private int mBottomStackSlowDownHeight;
84    private int mBottomStackPeekSize;
85    private int mEmptyMarginBottom;
86    private int mPaddingBetweenElements;
87    private int mPaddingBetweenElementsDimmed;
88    private int mPaddingBetweenElementsNormal;
89    private int mTopPadding;
90
91    /**
92     * The algorithm which calculates the properties for our children
93     */
94    private StackScrollAlgorithm mStackScrollAlgorithm;
95
96    /**
97     * The current State this Layout is in
98     */
99    private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
100    private AmbientState mAmbientState = new AmbientState();
101    private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
102    private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
103    private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
104    private ArrayList<View> mDragAnimPendingChildren = new ArrayList<View>();
105    private ArrayList<AnimationEvent> mAnimationEvents
106            = new ArrayList<AnimationEvent>();
107    private ArrayList<View> mSwipedOutViews = new ArrayList<View>();
108    private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
109
110    private OnChildLocationsChangedListener mListener;
111    private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
112    private boolean mNeedsAnimation;
113    private boolean mTopPaddingNeedsAnimation;
114    private boolean mDimmedNeedsAnimation;
115    private boolean mActivateNeedsAnimation;
116    private boolean mIsExpanded = true;
117    private boolean mChildrenUpdateRequested;
118    private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
119            = new ViewTreeObserver.OnPreDrawListener() {
120        @Override
121        public boolean onPreDraw() {
122            updateChildren();
123            mChildrenUpdateRequested = false;
124            getViewTreeObserver().removeOnPreDrawListener(this);
125            return true;
126        }
127    };
128
129    public NotificationStackScrollLayout(Context context) {
130        this(context, null);
131    }
132
133    public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
134        this(context, attrs, 0);
135    }
136
137    public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
138        this(context, attrs, defStyleAttr, 0);
139    }
140
141    public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
142            int defStyleRes) {
143        super(context, attrs, defStyleAttr, defStyleRes);
144        initView(context);
145        if (DEBUG) {
146            setWillNotDraw(false);
147            mDebugPaint = new Paint();
148            mDebugPaint.setColor(0xffff0000);
149            mDebugPaint.setStrokeWidth(2);
150            mDebugPaint.setStyle(Paint.Style.STROKE);
151        }
152    }
153
154    @Override
155    protected void onDraw(Canvas canvas) {
156        if (DEBUG) {
157            int y = mCollapsedSize;
158            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
159            y = (int) (getLayoutHeight() - mBottomStackPeekSize
160                    - mBottomStackSlowDownHeight);
161            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
162            y = (int) (getLayoutHeight() - mBottomStackPeekSize);
163            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
164            y = (int) getLayoutHeight();
165            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
166        }
167    }
168
169    private void initView(Context context) {
170        mScroller = new OverScroller(getContext());
171        setFocusable(true);
172        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
173        setClipChildren(false);
174        final ViewConfiguration configuration = ViewConfiguration.get(context);
175        mTouchSlop = configuration.getScaledTouchSlop();
176        mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
177        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
178        mOverscrollDistance = configuration.getScaledOverscrollDistance();
179        mOverflingDistance = configuration.getScaledOverflingDistance();
180        float densityScale = getResources().getDisplayMetrics().density;
181        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
182        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
183
184        mSidePaddings = context.getResources()
185                .getDimensionPixelSize(R.dimen.notification_side_padding);
186        mCollapsedSize = context.getResources()
187                .getDimensionPixelSize(R.dimen.notification_min_height);
188        mBottomStackPeekSize = context.getResources()
189                .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
190        mEmptyMarginBottom = context.getResources().getDimensionPixelSize(
191                R.dimen.notification_stack_margin_bottom);
192        mStackScrollAlgorithm = new StackScrollAlgorithm(context);
193        mPaddingBetweenElementsDimmed = context.getResources()
194                .getDimensionPixelSize(R.dimen.notification_padding_dimmed);
195        mPaddingBetweenElementsNormal = context.getResources()
196                .getDimensionPixelSize(R.dimen.notification_padding);
197        updatePadding(false);
198    }
199
200    private void updatePadding(boolean dimmed) {
201        mPaddingBetweenElements = dimmed
202                ? mPaddingBetweenElementsDimmed
203                : mPaddingBetweenElementsNormal;
204        mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
205        updateContentHeight();
206    }
207
208    @Override
209    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
210        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
211        int mode = MeasureSpec.getMode(widthMeasureSpec);
212        int size = MeasureSpec.getSize(widthMeasureSpec);
213        int childMeasureSpec = MeasureSpec.makeMeasureSpec(size - 2 * mSidePaddings, mode);
214        measureChildren(childMeasureSpec, heightMeasureSpec);
215    }
216
217    @Override
218    protected void onLayout(boolean changed, int l, int t, int r, int b) {
219
220        // we layout all our children centered on the top
221        float centerX = getWidth() / 2.0f;
222        for (int i = 0; i < getChildCount(); i++) {
223            View child = getChildAt(i);
224            float width = child.getMeasuredWidth();
225            float height = child.getMeasuredHeight();
226            child.layout((int) (centerX - width / 2.0f),
227                    0,
228                    (int) (centerX + width / 2.0f),
229                    (int) height);
230        }
231        setMaxLayoutHeight(getHeight() - mEmptyMarginBottom);
232        updateContentHeight();
233        updateScrollPositionIfNecessary();
234        requestChildrenUpdate();
235    }
236
237    public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
238        mListener = listener;
239    }
240
241    /**
242     * Returns the location the given child is currently rendered at.
243     *
244     * @param child the child to get the location for
245     * @return one of {@link ViewState}'s <code>LOCATION_*</code> constants
246     */
247    public int getChildLocation(View child) {
248        ViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
249        if (childViewState == null) {
250            return ViewState.LOCATION_UNKNOWN;
251        }
252        return childViewState.location;
253    }
254
255    private void setMaxLayoutHeight(int maxLayoutHeight) {
256        mMaxLayoutHeight = maxLayoutHeight;
257        updateAlgorithmHeightAndPadding();
258    }
259
260    private void updateAlgorithmHeightAndPadding() {
261        mStackScrollAlgorithm.setLayoutHeight(getLayoutHeight());
262        mStackScrollAlgorithm.setTopPadding(mTopPadding);
263    }
264
265    /**
266     * @return whether the height of the layout needs to be adapted, in order to ensure that the
267     *         last child is not in the bottom stack.
268     */
269    private boolean needsHeightAdaption() {
270        View lastChild = getLastChildNotGone();
271        View firstChild = getFirstChildNotGone();
272        boolean isLastChildExpanded = isViewExpanded(lastChild);
273        return isLastChildExpanded && lastChild != firstChild;
274    }
275
276    private boolean isViewExpanded(View view) {
277        if (view != null) {
278            ExpandableView expandView = (ExpandableView) view;
279            return expandView.getActualHeight() > mCollapsedSize;
280        }
281        return false;
282    }
283
284    /**
285     * Updates the children views according to the stack scroll algorithm. Call this whenever
286     * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
287     */
288    private void updateChildren() {
289        mAmbientState.setScrollY(mOwnScrollY);
290        mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
291        if (!isCurrentlyAnimating() && !mNeedsAnimation) {
292            applyCurrentState();
293        } else {
294            startAnimationToState();
295        }
296    }
297
298    private void requestChildrenUpdate() {
299        if (!mChildrenUpdateRequested) {
300            getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
301            mChildrenUpdateRequested = true;
302            invalidate();
303        }
304    }
305
306    private boolean isCurrentlyAnimating() {
307        return mStateAnimator.isRunning();
308    }
309
310    private void updateScrollPositionIfNecessary() {
311        int scrollRange = getScrollRange();
312        if (scrollRange < mOwnScrollY) {
313            mOwnScrollY = scrollRange;
314        }
315    }
316
317    public int getTopPadding() {
318        return mTopPadding;
319    }
320
321    public void setTopPadding(int topPadding, boolean animate) {
322        if (mTopPadding != topPadding) {
323            mTopPadding = topPadding;
324            updateAlgorithmHeightAndPadding();
325            updateContentHeight();
326            if (animate) {
327                mTopPaddingNeedsAnimation = true;
328                mNeedsAnimation =  true;
329            }
330            requestChildrenUpdate();
331            if (mOnHeightChangedListener != null) {
332                mOnHeightChangedListener.onHeightChanged(null);
333            }
334        }
335    }
336
337    /**
338     * Update the height of the stack to a new height.
339     *
340     * @param height the new height of the stack
341     */
342    public void setStackHeight(float height) {
343        setIsExpanded(height > 0.0f);
344        int newStackHeight = (int) height;
345        int itemHeight = getItemHeight();
346        int bottomStackPeekSize = mBottomStackPeekSize;
347        int minStackHeight = itemHeight + bottomStackPeekSize;
348        int stackHeight;
349        if (newStackHeight - mTopPadding >= minStackHeight) {
350            setTranslationY(0);
351            stackHeight = newStackHeight;
352        } else {
353
354            // We did not reach the position yet where we actually start growing,
355            // so we translate the stack upwards.
356            int translationY = (newStackHeight - minStackHeight);
357            // A slight parallax effect is introduced in order for the stack to catch up with
358            // the top card.
359            float partiallyThere = (float) (newStackHeight - mTopPadding) / minStackHeight;
360            partiallyThere = Math.max(0, partiallyThere);
361            translationY += (1 - partiallyThere) * bottomStackPeekSize;
362            setTranslationY(translationY - mTopPadding);
363            stackHeight = (int) (height - (translationY - mTopPadding));
364        }
365        if (stackHeight != mCurrentStackHeight) {
366            mCurrentStackHeight = stackHeight;
367            updateAlgorithmHeightAndPadding();
368            requestChildrenUpdate();
369        }
370    }
371
372    /**
373     * Get the current height of the view. This is at most the msize of the view given by a the
374     * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
375     *
376     * @return either the layout height or the externally defined height, whichever is smaller
377     */
378    private int getLayoutHeight() {
379        return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
380    }
381
382    public int getItemHeight() {
383        return mCollapsedSize;
384    }
385
386    public int getBottomStackPeekSize() {
387        return mBottomStackPeekSize;
388    }
389
390    public void setLongPressListener(View.OnLongClickListener listener) {
391        mSwipeHelper.setLongPressListener(listener);
392    }
393
394    public void onChildDismissed(View v) {
395        if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
396        final View veto = v.findViewById(R.id.veto);
397        if (veto != null && veto.getVisibility() != View.GONE) {
398            veto.performClick();
399        }
400        setSwipingInProgress(false);
401        if (mDragAnimPendingChildren.contains(v)) {
402            // We start the swipe and finish it in the same frame, we don't want any animation
403            // for the drag
404            mDragAnimPendingChildren.remove(v);
405        }
406        mSwipedOutViews.add(v);
407        mAmbientState.onDragFinished(v);
408    }
409
410    @Override
411    public void onChildSnappedBack(View animView) {
412        mAmbientState.onDragFinished(animView);
413        if (!mDragAnimPendingChildren.contains(animView)) {
414            mSnappedBackChildren.add(animView);
415            requestChildrenUpdate();
416            mNeedsAnimation = true;
417        } else {
418            // We start the swipe and snap back in the same frame, we don't want any animation
419            mDragAnimPendingChildren.remove(animView);
420        }
421    }
422
423    public void onBeginDrag(View v) {
424        setSwipingInProgress(true);
425        mDragAnimPendingChildren.add(v);
426        mAmbientState.onBeginDrag(v);
427        requestChildrenUpdate();
428        mNeedsAnimation = true;
429    }
430
431    public void onDragCancelled(View v) {
432        setSwipingInProgress(false);
433    }
434
435    public View getChildAtPosition(MotionEvent ev) {
436        return getChildAtPosition(ev.getX(), ev.getY());
437    }
438
439    public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
440        int[] location = new int[2];
441        getLocationOnScreen(location);
442        return getChildAtPosition(touchX - location[0], touchY - location[1]);
443    }
444
445    public ExpandableView getChildAtPosition(float touchX, float touchY) {
446        // find the view under the pointer, accounting for GONE views
447        final int count = getChildCount();
448        for (int childIdx = 0; childIdx < count; childIdx++) {
449            ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
450            if (slidingChild.getVisibility() == GONE) {
451                continue;
452            }
453            float top = slidingChild.getTranslationY();
454            float bottom = top + slidingChild.getActualHeight();
455            int left = slidingChild.getLeft();
456            int right = slidingChild.getRight();
457
458            if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
459                return slidingChild;
460            }
461        }
462        return null;
463    }
464
465    public boolean canChildBeExpanded(View v) {
466        return v instanceof ExpandableNotificationRow
467                && ((ExpandableNotificationRow) v).isExpandable();
468    }
469
470    public void setUserExpandedChild(View v, boolean userExpanded) {
471        if (v instanceof ExpandableNotificationRow) {
472            ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
473        }
474    }
475
476    public void setUserLockedChild(View v, boolean userLocked) {
477        if (v instanceof ExpandableNotificationRow) {
478            ((ExpandableNotificationRow) v).setUserLocked(userLocked);
479        }
480    }
481
482    public View getChildContentView(View v) {
483        return v;
484    }
485
486    public boolean canChildBeDismissed(View v) {
487        final View veto = v.findViewById(R.id.veto);
488        return (veto != null && veto.getVisibility() != View.GONE);
489    }
490
491    private void setSwipingInProgress(boolean isSwiped) {
492        mSwipingInProgress = isSwiped;
493        if(isSwiped) {
494            requestDisallowInterceptTouchEvent(true);
495        }
496    }
497
498    @Override
499    protected void onConfigurationChanged(Configuration newConfig) {
500        super.onConfigurationChanged(newConfig);
501        float densityScale = getResources().getDisplayMetrics().density;
502        mSwipeHelper.setDensityScale(densityScale);
503        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
504        mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
505        initView(getContext());
506    }
507
508    public void dismissRowAnimated(View child, int vel) {
509        mSwipeHelper.dismissChild(child, vel);
510    }
511
512    @Override
513    public boolean onTouchEvent(MotionEvent ev) {
514        if (!isEnabled()) {
515            return false;
516        }
517        boolean scrollerWantsIt = false;
518        if (!mSwipingInProgress) {
519            scrollerWantsIt = onScrollTouch(ev);
520        }
521        boolean horizontalSwipeWantsIt = false;
522        if (!mIsBeingDragged) {
523            horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
524        }
525        return horizontalSwipeWantsIt || scrollerWantsIt || super.onTouchEvent(ev);
526    }
527
528    private boolean onScrollTouch(MotionEvent ev) {
529        initVelocityTrackerIfNotExists();
530        mVelocityTracker.addMovement(ev);
531
532        final int action = ev.getAction();
533
534        switch (action & MotionEvent.ACTION_MASK) {
535            case MotionEvent.ACTION_DOWN: {
536                if (getChildCount() == 0 || !isInContentBounds(ev)) {
537                    return false;
538                }
539                boolean isBeingDragged = !mScroller.isFinished();
540                setIsBeingDragged(isBeingDragged);
541
542                /*
543                 * If being flinged and user touches, stop the fling. isFinished
544                 * will be false if being flinged.
545                 */
546                if (!mScroller.isFinished()) {
547                    mScroller.abortAnimation();
548                }
549
550                // Remember where the motion event started
551                mLastMotionY = (int) ev.getY();
552                mActivePointerId = ev.getPointerId(0);
553                break;
554            }
555            case MotionEvent.ACTION_MOVE:
556                final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
557                if (activePointerIndex == -1) {
558                    Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
559                    break;
560                }
561
562                final int y = (int) ev.getY(activePointerIndex);
563                int deltaY = mLastMotionY - y;
564                if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
565                    setIsBeingDragged(true);
566                    if (deltaY > 0) {
567                        deltaY -= mTouchSlop;
568                    } else {
569                        deltaY += mTouchSlop;
570                    }
571                }
572                if (mIsBeingDragged) {
573                    // Scroll to follow the motion event
574                    mLastMotionY = y;
575
576                    final int oldX = mScrollX;
577                    final int oldY = mOwnScrollY;
578                    final int range = getScrollRange();
579                    final int overscrollMode = getOverScrollMode();
580                    final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
581                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
582
583                    // Calling overScrollBy will call onOverScrolled, which
584                    // calls onScrollChanged if applicable.
585                    if (overScrollBy(0, deltaY, 0, mOwnScrollY,
586                            0, range, 0, mOverscrollDistance, true)) {
587                        // Break our velocity if we hit a scroll barrier.
588                        mVelocityTracker.clear();
589                    }
590                    // TODO: Overscroll
591//                    if (canOverscroll) {
592//                        final int pulledToY = oldY + deltaY;
593//                        if (pulledToY < 0) {
594//                            mEdgeGlowTop.onPull((float) deltaY / getHeight());
595//                            if (!mEdgeGlowBottom.isFinished()) {
596//                                mEdgeGlowBottom.onRelease();
597//                            }
598//                        } else if (pulledToY > range) {
599//                            mEdgeGlowBottom.onPull((float) deltaY / getHeight());
600//                            if (!mEdgeGlowTop.isFinished()) {
601//                                mEdgeGlowTop.onRelease();
602//                            }
603//                        }
604//                        if (mEdgeGlowTop != null
605//                                && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())){
606//                            postInvalidateOnAnimation();
607//                        }
608//                    }
609                }
610                break;
611            case MotionEvent.ACTION_UP:
612                if (mIsBeingDragged) {
613                    final VelocityTracker velocityTracker = mVelocityTracker;
614                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
615                    int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
616
617                    if (getChildCount() > 0) {
618                        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
619                            fling(-initialVelocity);
620                        } else {
621                            if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
622                                    getScrollRange())) {
623                                postInvalidateOnAnimation();
624                            }
625                        }
626                    }
627
628                    mActivePointerId = INVALID_POINTER;
629                    endDrag();
630                }
631                break;
632            case MotionEvent.ACTION_CANCEL:
633                if (mIsBeingDragged && getChildCount() > 0) {
634                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
635                        postInvalidateOnAnimation();
636                    }
637                    mActivePointerId = INVALID_POINTER;
638                    endDrag();
639                }
640                break;
641            case MotionEvent.ACTION_POINTER_DOWN: {
642                final int index = ev.getActionIndex();
643                mLastMotionY = (int) ev.getY(index);
644                mActivePointerId = ev.getPointerId(index);
645                break;
646            }
647            case MotionEvent.ACTION_POINTER_UP:
648                onSecondaryPointerUp(ev);
649                mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
650                break;
651        }
652        return true;
653    }
654
655    private void onSecondaryPointerUp(MotionEvent ev) {
656        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
657                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
658        final int pointerId = ev.getPointerId(pointerIndex);
659        if (pointerId == mActivePointerId) {
660            // This was our active pointer going up. Choose a new
661            // active pointer and adjust accordingly.
662            // TODO: Make this decision more intelligent.
663            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
664            mLastMotionY = (int) ev.getY(newPointerIndex);
665            mActivePointerId = ev.getPointerId(newPointerIndex);
666            if (mVelocityTracker != null) {
667                mVelocityTracker.clear();
668            }
669        }
670    }
671
672    private void initVelocityTrackerIfNotExists() {
673        if (mVelocityTracker == null) {
674            mVelocityTracker = VelocityTracker.obtain();
675        }
676    }
677
678    private void recycleVelocityTracker() {
679        if (mVelocityTracker != null) {
680            mVelocityTracker.recycle();
681            mVelocityTracker = null;
682        }
683    }
684
685    private void initOrResetVelocityTracker() {
686        if (mVelocityTracker == null) {
687            mVelocityTracker = VelocityTracker.obtain();
688        } else {
689            mVelocityTracker.clear();
690        }
691    }
692
693    @Override
694    public void computeScroll() {
695        if (mScroller.computeScrollOffset()) {
696            // This is called at drawing time by ViewGroup.
697            int oldX = mScrollX;
698            int oldY = mOwnScrollY;
699            int x = mScroller.getCurrX();
700            int y = mScroller.getCurrY();
701
702            if (oldX != x || oldY != y) {
703                final int range = getScrollRange();
704                final int overscrollMode = getOverScrollMode();
705                final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
706                        (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
707
708                overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
709                        0, mOverflingDistance, false);
710                onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
711
712                if (canOverscroll) {
713                    // TODO: Overscroll
714//                    if (y < 0 && oldY >= 0) {
715//                        mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
716//                    } else if (y > range && oldY <= range) {
717//                        mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
718//                    }
719                }
720                updateChildren();
721            }
722
723            // Keep on drawing until the animation has finished.
724            postInvalidateOnAnimation();
725        }
726    }
727
728    private void customScrollTo(int y) {
729        mOwnScrollY = y;
730        updateChildren();
731    }
732
733    @Override
734    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
735        // Treat animating scrolls differently; see #computeScroll() for why.
736        if (!mScroller.isFinished()) {
737            final int oldX = mScrollX;
738            final int oldY = mOwnScrollY;
739            mScrollX = scrollX;
740            mOwnScrollY = scrollY;
741            invalidateParentIfNeeded();
742            onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
743            if (clampedY) {
744                mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange());
745            }
746            updateChildren();
747        } else {
748            customScrollTo(scrollY);
749            scrollTo(scrollX, mScrollY);
750        }
751    }
752
753    private int getScrollRange() {
754        int scrollRange = 0;
755        ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
756        if (firstChild != null) {
757            int contentHeight = getContentHeight();
758            int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
759            scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
760                    + mBottomStackSlowDownHeight);
761            if (scrollRange > 0) {
762                View lastChild = getLastChildNotGone();
763                // We want to at least be able collapse the first item and not ending in a weird
764                // end state.
765                scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
766            }
767        }
768        return scrollRange;
769    }
770
771    /**
772     * @return the first child which has visibility unequal to GONE
773     */
774    private View getFirstChildNotGone() {
775        int childCount = getChildCount();
776        for (int i = 0; i < childCount; i++) {
777            View child = getChildAt(i);
778            if (child.getVisibility() != View.GONE) {
779                return child;
780            }
781        }
782        return null;
783    }
784
785    /**
786     * @return the last child which has visibility unequal to GONE
787     */
788    private View getLastChildNotGone() {
789        int childCount = getChildCount();
790        for (int i = childCount - 1; i >= 0; i--) {
791            View child = getChildAt(i);
792            if (child.getVisibility() != View.GONE) {
793                return child;
794            }
795        }
796        return null;
797    }
798
799    private int getMaxExpandHeight(View view) {
800        if (view instanceof ExpandableNotificationRow) {
801            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
802            return row.getIntrinsicHeight();
803        }
804        return view.getHeight();
805    }
806
807    private int getContentHeight() {
808        return mContentHeight;
809    }
810
811    private void updateContentHeight() {
812        int height = 0;
813        for (int i = 0; i < getChildCount(); i++) {
814            View child = getChildAt(i);
815            if (child.getVisibility() != View.GONE) {
816                if (height != 0) {
817                    // add the padding before this element
818                    height += mPaddingBetweenElements;
819                }
820                if (child instanceof ExpandableNotificationRow) {
821                    ExpandableNotificationRow row = (ExpandableNotificationRow) child;
822                    height += row.getIntrinsicHeight();
823                } else if (child instanceof ExpandableView) {
824                    ExpandableView expandableView = (ExpandableView) child;
825                    height += expandableView.getActualHeight();
826                }
827            }
828        }
829        mContentHeight = height + mTopPadding;
830    }
831
832    /**
833     * Fling the scroll view
834     *
835     * @param velocityY The initial velocity in the Y direction. Positive
836     *                  numbers mean that the finger/cursor is moving down the screen,
837     *                  which means we want to scroll towards the top.
838     */
839    private void fling(int velocityY) {
840        if (getChildCount() > 0) {
841            int height = (int) getLayoutHeight();
842            int bottom = getContentHeight();
843
844            mScroller.fling(mScrollX, mOwnScrollY, 0, velocityY, 0, 0, 0,
845                    Math.max(0, bottom - height), 0, height/2);
846
847            postInvalidateOnAnimation();
848        }
849    }
850
851    private void endDrag() {
852        setIsBeingDragged(false);
853
854        recycleVelocityTracker();
855
856        // TODO: Overscroll
857//        if (mEdgeGlowTop != null) {
858//            mEdgeGlowTop.onRelease();
859//            mEdgeGlowBottom.onRelease();
860//        }
861    }
862
863    @Override
864    public boolean onInterceptTouchEvent(MotionEvent ev) {
865        boolean scrollWantsIt = false;
866        if (!mSwipingInProgress) {
867            scrollWantsIt = onInterceptTouchEventScroll(ev);
868        }
869        boolean swipeWantsIt = false;
870        if (!mIsBeingDragged) {
871            swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
872        }
873        return swipeWantsIt || scrollWantsIt ||
874                super.onInterceptTouchEvent(ev);
875    }
876
877    @Override
878    protected void onViewRemoved(View child) {
879        super.onViewRemoved(child);
880        ((ExpandableView) child).setOnHeightChangedListener(null);
881        mCurrentStackScrollState.removeViewStateForView(child);
882        mStackScrollAlgorithm.notifyChildrenChanged(this);
883        updateScrollStateForRemovedChild(child);
884        if (mIsExpanded) {
885
886            if (!mChildrenToAddAnimated.contains(child)) {
887                // Generate Animations
888                mChildrenToRemoveAnimated.add(child);
889                mNeedsAnimation = true;
890            } else {
891                mChildrenToAddAnimated.remove(child);
892            }
893        }
894    }
895
896    /**
897     * Updates the scroll position when a child was removed
898     *
899     * @param removedChild the removed child
900     */
901    private void updateScrollStateForRemovedChild(View removedChild) {
902        int startingPosition = getPositionInLinearLayout(removedChild);
903        int childHeight = removedChild.getHeight() + mPaddingBetweenElements;
904        int endPosition = startingPosition + childHeight;
905        if (endPosition <= mOwnScrollY) {
906            // This child is fully scrolled of the top, so we have to deduct its height from the
907            // scrollPosition
908            mOwnScrollY -= childHeight;
909        } else if (startingPosition < mOwnScrollY) {
910            // This child is currently being scrolled into, set the scroll position to the start of
911            // this child
912            mOwnScrollY = startingPosition;
913        }
914    }
915
916    private int getPositionInLinearLayout(View requestedChild) {
917        int position = 0;
918        for (int i = 0; i < getChildCount(); i++) {
919            View child = getChildAt(i);
920            if (child == requestedChild) {
921                return position;
922            }
923            if (child.getVisibility() != View.GONE) {
924                position += child.getHeight();
925                if (i < getChildCount()-1) {
926                    position += mPaddingBetweenElements;
927                }
928            }
929        }
930        return 0;
931    }
932
933    @Override
934    protected void onViewAdded(View child) {
935        super.onViewAdded(child);
936        mStackScrollAlgorithm.notifyChildrenChanged(this);
937        ((ExpandableView) child).setOnHeightChangedListener(this);
938        if (child.getVisibility() != View.GONE) {
939            generateAddAnimation(child);
940        }
941    }
942
943    public void generateAddAnimation(View child) {
944        if (mIsExpanded) {
945
946            // Generate Animations
947            mChildrenToAddAnimated.add(child);
948            mNeedsAnimation = true;
949        }
950    }
951
952    /**
953     * Change the position of child to a new location
954     *
955     * @param child the view to change the position for
956     * @param newIndex the new index
957     */
958    public void changeViewPosition(View child, int newIndex) {
959        if (child != null && child.getParent() == this) {
960            // TODO: handle this
961        }
962    }
963
964    private void startAnimationToState() {
965        if (mNeedsAnimation) {
966            generateChildHierarchyEvents();
967            mNeedsAnimation = false;
968        }
969        if (!mAnimationEvents.isEmpty()) {
970            mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState);
971        } else {
972            applyCurrentState();
973        }
974    }
975
976    private void generateChildHierarchyEvents() {
977        generateChildAdditionEvents();
978        generateChildRemovalEvents();
979        generateSnapBackEvents();
980        generateDragEvents();
981        generateTopPaddingEvent();
982        generateActivateEvent();
983        generateDimmedEvent();
984        mNeedsAnimation = false;
985    }
986
987    private void generateSnapBackEvents() {
988        for (View child : mSnappedBackChildren) {
989            mAnimationEvents.add(new AnimationEvent(child,
990                    AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
991        }
992        mSnappedBackChildren.clear();
993    }
994
995    private void generateDragEvents() {
996        for (View child : mDragAnimPendingChildren) {
997            mAnimationEvents.add(new AnimationEvent(child,
998                    AnimationEvent.ANIMATION_TYPE_START_DRAG));
999        }
1000        mDragAnimPendingChildren.clear();
1001    }
1002
1003    private void generateChildRemovalEvents() {
1004        for (View child : mChildrenToRemoveAnimated) {
1005            boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1006            int animationType = childWasSwipedOut
1007                    ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1008                    : AnimationEvent.ANIMATION_TYPE_REMOVE;
1009            mAnimationEvents.add(new AnimationEvent(child, animationType));
1010        }
1011        mSwipedOutViews.clear();
1012        mChildrenToRemoveAnimated.clear();
1013    }
1014
1015    private void generateChildAdditionEvents() {
1016        for (View child : mChildrenToAddAnimated) {
1017            mAnimationEvents.add(new AnimationEvent(child,
1018                    AnimationEvent.ANIMATION_TYPE_ADD));
1019        }
1020        mChildrenToAddAnimated.clear();
1021    }
1022
1023    private void generateTopPaddingEvent() {
1024        if (mTopPaddingNeedsAnimation) {
1025            mAnimationEvents.add(
1026                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
1027        }
1028        mTopPaddingNeedsAnimation = false;
1029    }
1030
1031    private void generateActivateEvent() {
1032        if (mActivateNeedsAnimation) {
1033            mAnimationEvents.add(
1034                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
1035        }
1036        mActivateNeedsAnimation = false;
1037    }
1038
1039    private void generateDimmedEvent() {
1040        if (mDimmedNeedsAnimation) {
1041            mAnimationEvents.add(
1042                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
1043        }
1044        mDimmedNeedsAnimation = false;
1045    }
1046
1047    private boolean onInterceptTouchEventScroll(MotionEvent ev) {
1048        /*
1049         * This method JUST determines whether we want to intercept the motion.
1050         * If we return true, onMotionEvent will be called and we do the actual
1051         * scrolling there.
1052         */
1053
1054        /*
1055        * Shortcut the most recurring case: the user is in the dragging
1056        * state and he is moving his finger.  We want to intercept this
1057        * motion.
1058        */
1059        final int action = ev.getAction();
1060        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
1061            return true;
1062        }
1063
1064        /*
1065         * Don't try to intercept touch if we can't scroll anyway.
1066         */
1067        if (mOwnScrollY == 0 && getScrollRange() == 0) {
1068            return false;
1069        }
1070
1071        switch (action & MotionEvent.ACTION_MASK) {
1072            case MotionEvent.ACTION_MOVE: {
1073                /*
1074                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1075                 * whether the user has moved far enough from his original down touch.
1076                 */
1077
1078                /*
1079                * Locally do absolute value. mLastMotionY is set to the y value
1080                * of the down event.
1081                */
1082                final int activePointerId = mActivePointerId;
1083                if (activePointerId == INVALID_POINTER) {
1084                    // If we don't have a valid id, the touch down wasn't on content.
1085                    break;
1086                }
1087
1088                final int pointerIndex = ev.findPointerIndex(activePointerId);
1089                if (pointerIndex == -1) {
1090                    Log.e(TAG, "Invalid pointerId=" + activePointerId
1091                            + " in onInterceptTouchEvent");
1092                    break;
1093                }
1094
1095                final int y = (int) ev.getY(pointerIndex);
1096                final int yDiff = Math.abs(y - mLastMotionY);
1097                if (yDiff > mTouchSlop) {
1098                    setIsBeingDragged(true);
1099                    mLastMotionY = y;
1100                    initVelocityTrackerIfNotExists();
1101                    mVelocityTracker.addMovement(ev);
1102                }
1103                break;
1104            }
1105
1106            case MotionEvent.ACTION_DOWN: {
1107                final int y = (int) ev.getY();
1108                if (getChildAtPosition(ev.getX(), y) == null) {
1109                    setIsBeingDragged(false);
1110                    recycleVelocityTracker();
1111                    break;
1112                }
1113
1114                /*
1115                 * Remember location of down touch.
1116                 * ACTION_DOWN always refers to pointer index 0.
1117                 */
1118                mLastMotionY = y;
1119                mActivePointerId = ev.getPointerId(0);
1120
1121                initOrResetVelocityTracker();
1122                mVelocityTracker.addMovement(ev);
1123                /*
1124                * If being flinged and user touches the screen, initiate drag;
1125                * otherwise don't.  mScroller.isFinished should be false when
1126                * being flinged.
1127                */
1128                boolean isBeingDragged = !mScroller.isFinished();
1129                setIsBeingDragged(isBeingDragged);
1130                break;
1131            }
1132
1133            case MotionEvent.ACTION_CANCEL:
1134            case MotionEvent.ACTION_UP:
1135                /* Release the drag */
1136                setIsBeingDragged(false);
1137                mActivePointerId = INVALID_POINTER;
1138                recycleVelocityTracker();
1139                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1140                    postInvalidateOnAnimation();
1141                }
1142                break;
1143            case MotionEvent.ACTION_POINTER_UP:
1144                onSecondaryPointerUp(ev);
1145                break;
1146        }
1147
1148        /*
1149        * The only time we want to intercept motion events is if we are in the
1150        * drag mode.
1151        */
1152        return mIsBeingDragged;
1153    }
1154
1155    /**
1156     * @return Whether the specified motion event is actually happening over the content.
1157     */
1158    private boolean isInContentBounds(MotionEvent event) {
1159        return event.getY() < getHeight() - getEmptyBottomMargin();
1160    }
1161
1162    private void setIsBeingDragged(boolean isDragged) {
1163        mIsBeingDragged = isDragged;
1164        if (isDragged) {
1165            requestDisallowInterceptTouchEvent(true);
1166            mSwipeHelper.removeLongPressCallback();
1167        }
1168    }
1169
1170    @Override
1171    public void onWindowFocusChanged(boolean hasWindowFocus) {
1172        super.onWindowFocusChanged(hasWindowFocus);
1173        if (!hasWindowFocus) {
1174            mSwipeHelper.removeLongPressCallback();
1175        }
1176    }
1177
1178    @Override
1179    public boolean isScrolledToTop() {
1180        return mOwnScrollY == 0;
1181    }
1182
1183    @Override
1184    public boolean isScrolledToBottom() {
1185        return mOwnScrollY >= getScrollRange();
1186    }
1187
1188    @Override
1189    public View getHostView() {
1190        return this;
1191    }
1192
1193    public int getEmptyBottomMargin() {
1194        int emptyMargin = mMaxLayoutHeight - mContentHeight;
1195        if (needsHeightAdaption()) {
1196            emptyMargin = emptyMargin - mBottomStackSlowDownHeight - mBottomStackPeekSize;
1197        }
1198        return Math.max(emptyMargin, 0);
1199    }
1200
1201    public void onExpansionStarted() {
1202        mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
1203    }
1204
1205    public void onExpansionStopped() {
1206        mStackScrollAlgorithm.onExpansionStopped();
1207    }
1208
1209    private void setIsExpanded(boolean isExpanded) {
1210        mIsExpanded = isExpanded;
1211        mStackScrollAlgorithm.setIsExpanded(isExpanded);
1212        if (!isExpanded) {
1213            mOwnScrollY = 0;
1214        }
1215    }
1216
1217    @Override
1218    public void onHeightChanged(ExpandableView view) {
1219        updateContentHeight();
1220        updateScrollPositionIfNecessary();
1221        if (mOnHeightChangedListener != null) {
1222            mOnHeightChangedListener.onHeightChanged(view);
1223        }
1224        requestChildrenUpdate();
1225    }
1226
1227    public void setOnHeightChangedListener(
1228            ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
1229        this.mOnHeightChangedListener = mOnHeightChangedListener;
1230    }
1231
1232    public void onChildAnimationFinished() {
1233        requestChildrenUpdate();
1234        mAnimationEvents.clear();
1235    }
1236
1237    /**
1238     * See {@link AmbientState#setDimmed}.
1239     */
1240    public void setDimmed(boolean dimmed, boolean animate) {
1241        mStackScrollAlgorithm.setDimmed(dimmed);
1242        mAmbientState.setDimmed(dimmed);
1243        updatePadding(dimmed);
1244        if (animate) {
1245            mDimmedNeedsAnimation = true;
1246            mNeedsAnimation =  true;
1247        }
1248        requestChildrenUpdate();
1249    }
1250
1251    /**
1252     * See {@link AmbientState#setActivatedChild}.
1253     */
1254    public void setActivatedChild(View activatedChild) {
1255        mAmbientState.setActivatedChild(activatedChild);
1256        mActivateNeedsAnimation = true;
1257        mNeedsAnimation =  true;
1258        requestChildrenUpdate();
1259    }
1260
1261    public View getActivatedChild() {
1262        return mAmbientState.getActivatedChild();
1263    }
1264
1265    private void applyCurrentState() {
1266        mCurrentStackScrollState.apply();
1267        if (mListener != null) {
1268            mListener.onChildLocationsChanged(this);
1269        }
1270    }
1271
1272    /**
1273     * A listener that is notified when some child locations might have changed.
1274     */
1275    public interface OnChildLocationsChangedListener {
1276        public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
1277    }
1278
1279    static class AnimationEvent {
1280
1281        static AnimationFilter[] FILTERS = new AnimationFilter[] {
1282
1283                // ANIMATION_TYPE_ADD
1284                new AnimationFilter()
1285                        .animateAlpha()
1286                        .animateHeight()
1287                        .animateY()
1288                        .animateZ(),
1289
1290                // ANIMATION_TYPE_REMOVE
1291                new AnimationFilter()
1292                        .animateAlpha()
1293                        .animateHeight()
1294                        .animateY()
1295                        .animateZ(),
1296
1297                // ANIMATION_TYPE_REMOVE_SWIPED_OUT
1298                new AnimationFilter()
1299                        .animateAlpha()
1300                        .animateHeight()
1301                        .animateY()
1302                        .animateZ(),
1303
1304                // ANIMATION_TYPE_TOP_PADDING_CHANGED
1305                new AnimationFilter()
1306                        .animateAlpha()
1307                        .animateHeight()
1308                        .animateY()
1309                        .animateDimmed()
1310                        .animateScale()
1311                        .animateZ(),
1312
1313                // ANIMATION_TYPE_START_DRAG
1314                new AnimationFilter()
1315                        .animateAlpha(),
1316
1317                // ANIMATION_TYPE_SNAP_BACK
1318                new AnimationFilter()
1319                        .animateAlpha(),
1320
1321                // ANIMATION_TYPE_ACTIVATED_CHILD
1322                new AnimationFilter()
1323                        .animateScale()
1324                        .animateAlpha(),
1325
1326                // ANIMATION_TYPE_DIMMED
1327                new AnimationFilter()
1328                        .animateY()
1329                        .animateScale()
1330                        .animateDimmed()
1331        };
1332
1333        static int[] LENGTHS = new int[] {
1334
1335                // ANIMATION_TYPE_ADD
1336                StackStateAnimator.ANIMATION_DURATION_STANDARD,
1337
1338                // ANIMATION_TYPE_REMOVE
1339                StackStateAnimator.ANIMATION_DURATION_STANDARD,
1340
1341                // ANIMATION_TYPE_REMOVE_SWIPED_OUT
1342                StackStateAnimator.ANIMATION_DURATION_STANDARD,
1343
1344                // ANIMATION_TYPE_TOP_PADDING_CHANGED
1345                StackStateAnimator.ANIMATION_DURATION_STANDARD,
1346
1347                // ANIMATION_TYPE_START_DRAG
1348                StackStateAnimator.ANIMATION_DURATION_STANDARD,
1349
1350                // ANIMATION_TYPE_SNAP_BACK
1351                StackStateAnimator.ANIMATION_DURATION_STANDARD,
1352
1353                // ANIMATION_TYPE_ACTIVATED_CHILD
1354                StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
1355
1356                // ANIMATION_TYPE_DIMMED
1357                StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
1358        };
1359
1360        static int ANIMATION_TYPE_ADD = 0;
1361        static int ANIMATION_TYPE_REMOVE = 1;
1362        static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
1363        static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
1364        static int ANIMATION_TYPE_START_DRAG = 4;
1365        static int ANIMATION_TYPE_SNAP_BACK = 5;
1366        static int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
1367        static int ANIMATION_TYPE_DIMMED = 7;
1368
1369        final long eventStartTime;
1370        final View changingView;
1371        final int animationType;
1372        final AnimationFilter filter;
1373        final long length;
1374
1375        AnimationEvent(View view, int type) {
1376            eventStartTime = AnimationUtils.currentAnimationTimeMillis();
1377            changingView = view;
1378            animationType = type;
1379            filter = FILTERS[type];
1380            length = LENGTHS[type];
1381        }
1382
1383        /**
1384         * Combines the length of several animation events into a single value.
1385         *
1386         * @param events The events of the lengths to combine.
1387         * @return The combined length. This is just the maximum of all length.
1388         */
1389        static long combineLength(ArrayList<AnimationEvent> events) {
1390            long length = 0;
1391            int size = events.size();
1392            for (int i = 0; i < size; i++) {
1393                length = Math.max(length, events.get(i).length);
1394            }
1395            return length;
1396        }
1397    }
1398
1399}
1400