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