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