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