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