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