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