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