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