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