NotificationStackScrollLayout.java revision 28f0e5932944d9abc4b6879b1d05523e9341c385
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
572            // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
573            // camera affordance).
574            int left = 0;
575            int right = getWidth();
576
577            if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
578                return slidingChild;
579            }
580        }
581        return null;
582    }
583
584    public boolean canChildBeExpanded(View v) {
585        return v instanceof ExpandableNotificationRow
586                && ((ExpandableNotificationRow) v).isExpandable();
587    }
588
589    public void setUserExpandedChild(View v, boolean userExpanded) {
590        if (v instanceof ExpandableNotificationRow) {
591            ((ExpandableNotificationRow) v).setUserExpanded(userExpanded);
592        }
593    }
594
595    public void setUserLockedChild(View v, boolean userLocked) {
596        if (v instanceof ExpandableNotificationRow) {
597            ((ExpandableNotificationRow) v).setUserLocked(userLocked);
598        }
599        removeLongPressCallback();
600        requestDisallowInterceptTouchEvent(true);
601    }
602
603    @Override
604    public void expansionStateChanged(boolean isExpanding) {
605        mExpandingNotification = isExpanding;
606        if (!mExpandedInThisMotion) {
607            mMaxScrollAfterExpand = mOwnScrollY;
608            mExpandedInThisMotion = true;
609        }
610    }
611
612    public void setScrollingEnabled(boolean enable) {
613        mScrollingEnabled = enable;
614    }
615
616    public void setExpandingEnabled(boolean enable) {
617        mExpandHelper.setEnabled(enable);
618    }
619
620    private boolean isScrollingEnabled() {
621        return mScrollingEnabled;
622    }
623
624    public View getChildContentView(View v) {
625        return v;
626    }
627
628    public boolean canChildBeDismissed(View v) {
629        final View veto = v.findViewById(R.id.veto);
630        return (veto != null && veto.getVisibility() != View.GONE);
631    }
632
633    private void setSwipingInProgress(boolean isSwiped) {
634        mSwipingInProgress = isSwiped;
635        if(isSwiped) {
636            requestDisallowInterceptTouchEvent(true);
637        }
638    }
639
640    @Override
641    protected void onConfigurationChanged(Configuration newConfig) {
642        super.onConfigurationChanged(newConfig);
643        float densityScale = getResources().getDisplayMetrics().density;
644        mSwipeHelper.setDensityScale(densityScale);
645        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
646        mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
647        initView(getContext());
648    }
649
650    public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
651        child.setClipBounds(null);
652        mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
653    }
654
655    @Override
656    public boolean onTouchEvent(MotionEvent ev) {
657        boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
658                || ev.getActionMasked()== MotionEvent.ACTION_UP;
659        if (mDelegateToScrollView) {
660            if (isCancelOrUp) {
661                mDelegateToScrollView = false;
662            }
663            transformTouchEvent(ev, this, mScrollView);
664            return mScrollView.onTouchEvent(ev);
665        }
666        boolean expandWantsIt = false;
667        if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
668            if (isCancelOrUp) {
669                mExpandHelper.onlyObserveMovements(false);
670            }
671            boolean wasExpandingBefore = mExpandingNotification;
672            expandWantsIt = mExpandHelper.onTouchEvent(ev);
673            if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
674                    && !mDisallowScrollingInThisMotion) {
675                dispatchDownEventToScroller(ev);
676            }
677        }
678        boolean scrollerWantsIt = false;
679        if (!mSwipingInProgress && !mExpandingNotification && !mDisallowScrollingInThisMotion) {
680            scrollerWantsIt = onScrollTouch(ev);
681        }
682        boolean horizontalSwipeWantsIt = false;
683        if (!mIsBeingDragged
684                && !mExpandingNotification
685                && !mExpandedInThisMotion
686                && !mOnlyScrollingInThisMotion) {
687            horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
688        }
689        return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
690    }
691
692    private void dispatchDownEventToScroller(MotionEvent ev) {
693        MotionEvent downEvent = MotionEvent.obtain(ev);
694        downEvent.setAction(MotionEvent.ACTION_DOWN);
695        onScrollTouch(downEvent);
696        downEvent.recycle();
697    }
698
699    private boolean onScrollTouch(MotionEvent ev) {
700        if (!isScrollingEnabled()) {
701            return false;
702        }
703        initVelocityTrackerIfNotExists();
704        mVelocityTracker.addMovement(ev);
705
706        final int action = ev.getAction();
707
708        switch (action & MotionEvent.ACTION_MASK) {
709            case MotionEvent.ACTION_DOWN: {
710                if (getChildCount() == 0 || !isInContentBounds(ev)) {
711                    return false;
712                }
713                boolean isBeingDragged = !mScroller.isFinished();
714                setIsBeingDragged(isBeingDragged);
715
716                /*
717                 * If being flinged and user touches, stop the fling. isFinished
718                 * will be false if being flinged.
719                 */
720                if (!mScroller.isFinished()) {
721                    mScroller.forceFinished(true);
722                }
723
724                // Remember where the motion event started
725                mLastMotionY = (int) ev.getY();
726                mDownX = (int) ev.getX();
727                mActivePointerId = ev.getPointerId(0);
728                break;
729            }
730            case MotionEvent.ACTION_MOVE:
731                final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
732                if (activePointerIndex == -1) {
733                    Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
734                    break;
735                }
736
737                final int y = (int) ev.getY(activePointerIndex);
738                final int x = (int) ev.getX(activePointerIndex);
739                int deltaY = mLastMotionY - y;
740                final int xDiff = Math.abs(x - mDownX);
741                final int yDiff = Math.abs(deltaY);
742                if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
743                    setIsBeingDragged(true);
744                    if (deltaY > 0) {
745                        deltaY -= mTouchSlop;
746                    } else {
747                        deltaY += mTouchSlop;
748                    }
749                }
750                if (mIsBeingDragged) {
751                    // Scroll to follow the motion event
752                    mLastMotionY = y;
753                    int range = getScrollRange();
754                    if (mExpandedInThisMotion) {
755                        range = Math.min(range, mMaxScrollAfterExpand);
756                    }
757
758                    float scrollAmount;
759                    if (deltaY < 0) {
760                        scrollAmount = overScrollDown(deltaY);
761                    } else {
762                        scrollAmount = overScrollUp(deltaY, range);
763                    }
764
765                    // Calling overScrollBy will call onOverScrolled, which
766                    // calls onScrollChanged if applicable.
767                    if (scrollAmount != 0.0f) {
768                        // The scrolling motion could not be compensated with the
769                        // existing overScroll, we have to scroll the view
770                        overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
771                                0, range, 0, getHeight() / 2, true);
772                    }
773                }
774                break;
775            case MotionEvent.ACTION_UP:
776                if (mIsBeingDragged) {
777                    final VelocityTracker velocityTracker = mVelocityTracker;
778                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
779                    int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
780
781                    if (shouldOverScrollFling(initialVelocity)) {
782                        onOverScrollFling(true, initialVelocity);
783                    } else {
784                        if (getChildCount() > 0) {
785                            if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
786                                float currentOverScrollTop = getCurrentOverScrollAmount(true);
787                                if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
788                                    fling(-initialVelocity);
789                                } else {
790                                    onOverScrollFling(false, initialVelocity);
791                                }
792                            } else {
793                                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
794                                        getScrollRange())) {
795                                    postInvalidateOnAnimation();
796                                }
797                            }
798                        }
799                    }
800
801                    mActivePointerId = INVALID_POINTER;
802                    endDrag();
803                }
804
805                break;
806            case MotionEvent.ACTION_CANCEL:
807                if (mIsBeingDragged && getChildCount() > 0) {
808                    if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
809                        postInvalidateOnAnimation();
810                    }
811                    mActivePointerId = INVALID_POINTER;
812                    endDrag();
813                }
814                break;
815            case MotionEvent.ACTION_POINTER_DOWN: {
816                final int index = ev.getActionIndex();
817                mLastMotionY = (int) ev.getY(index);
818                mDownX = (int) ev.getX(index);
819                mActivePointerId = ev.getPointerId(index);
820                break;
821            }
822            case MotionEvent.ACTION_POINTER_UP:
823                onSecondaryPointerUp(ev);
824                mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
825                mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
826                break;
827        }
828        return true;
829    }
830
831    private void onOverScrollFling(boolean open, int initialVelocity) {
832        if (mOverscrollTopChangedListener != null) {
833            mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
834        }
835        mDontReportNextOverScroll = true;
836        setOverScrollAmount(0.0f, true, false);
837    }
838
839    /**
840     * Perform a scroll upwards and adapt the overscroll amounts accordingly
841     *
842     * @param deltaY The amount to scroll upwards, has to be positive.
843     * @return The amount of scrolling to be performed by the scroller,
844     *         not handled by the overScroll amount.
845     */
846    private float overScrollUp(int deltaY, int range) {
847        deltaY = Math.max(deltaY, 0);
848        float currentTopAmount = getCurrentOverScrollAmount(true);
849        float newTopAmount = currentTopAmount - deltaY;
850        if (currentTopAmount > 0) {
851            setOverScrollAmount(newTopAmount, true /* onTop */,
852                    false /* animate */);
853        }
854        // Top overScroll might not grab all scrolling motion,
855        // we have to scroll as well.
856        float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
857        float newScrollY = mOwnScrollY + scrollAmount;
858        if (newScrollY > range) {
859            if (!mExpandedInThisMotion) {
860                float currentBottomPixels = getCurrentOverScrolledPixels(false);
861                // We overScroll on the top
862                setOverScrolledPixels(currentBottomPixels + newScrollY - range,
863                        false /* onTop */,
864                        false /* animate */);
865            }
866            mOwnScrollY = range;
867            scrollAmount = 0.0f;
868        }
869        return scrollAmount;
870    }
871
872    /**
873     * Perform a scroll downward and adapt the overscroll amounts accordingly
874     *
875     * @param deltaY The amount to scroll downwards, has to be negative.
876     * @return The amount of scrolling to be performed by the scroller,
877     *         not handled by the overScroll amount.
878     */
879    private float overScrollDown(int deltaY) {
880        deltaY = Math.min(deltaY, 0);
881        float currentBottomAmount = getCurrentOverScrollAmount(false);
882        float newBottomAmount = currentBottomAmount + deltaY;
883        if (currentBottomAmount > 0) {
884            setOverScrollAmount(newBottomAmount, false /* onTop */,
885                    false /* animate */);
886        }
887        // Bottom overScroll might not grab all scrolling motion,
888        // we have to scroll as well.
889        float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
890        float newScrollY = mOwnScrollY + scrollAmount;
891        if (newScrollY < 0) {
892            float currentTopPixels = getCurrentOverScrolledPixels(true);
893            // We overScroll on the top
894            setOverScrolledPixels(currentTopPixels - newScrollY,
895                    true /* onTop */,
896                    false /* animate */);
897            mOwnScrollY = 0;
898            scrollAmount = 0.0f;
899        }
900        return scrollAmount;
901    }
902
903    private void onSecondaryPointerUp(MotionEvent ev) {
904        final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
905                MotionEvent.ACTION_POINTER_INDEX_SHIFT;
906        final int pointerId = ev.getPointerId(pointerIndex);
907        if (pointerId == mActivePointerId) {
908            // This was our active pointer going up. Choose a new
909            // active pointer and adjust accordingly.
910            // TODO: Make this decision more intelligent.
911            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
912            mLastMotionY = (int) ev.getY(newPointerIndex);
913            mActivePointerId = ev.getPointerId(newPointerIndex);
914            if (mVelocityTracker != null) {
915                mVelocityTracker.clear();
916            }
917        }
918    }
919
920    private void initVelocityTrackerIfNotExists() {
921        if (mVelocityTracker == null) {
922            mVelocityTracker = VelocityTracker.obtain();
923        }
924    }
925
926    private void recycleVelocityTracker() {
927        if (mVelocityTracker != null) {
928            mVelocityTracker.recycle();
929            mVelocityTracker = null;
930        }
931    }
932
933    private void initOrResetVelocityTracker() {
934        if (mVelocityTracker == null) {
935            mVelocityTracker = VelocityTracker.obtain();
936        } else {
937            mVelocityTracker.clear();
938        }
939    }
940
941    @Override
942    public void computeScroll() {
943        if (mScroller.computeScrollOffset()) {
944            // This is called at drawing time by ViewGroup.
945            int oldX = mScrollX;
946            int oldY = mOwnScrollY;
947            int x = mScroller.getCurrX();
948            int y = mScroller.getCurrY();
949
950            if (oldX != x || oldY != y) {
951                final int range = getScrollRange();
952                if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
953                    float currVelocity = mScroller.getCurrVelocity();
954                    if (currVelocity >= mMinimumVelocity) {
955                        mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
956                    }
957                }
958
959                overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
960                        0, (int) (mMaxOverScroll), false);
961                onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
962            }
963
964            // Keep on drawing until the animation has finished.
965            postInvalidateOnAnimation();
966        }
967    }
968
969    @Override
970    protected boolean overScrollBy(int deltaX, int deltaY,
971            int scrollX, int scrollY,
972            int scrollRangeX, int scrollRangeY,
973            int maxOverScrollX, int maxOverScrollY,
974            boolean isTouchEvent) {
975
976        int newScrollY = scrollY + deltaY;
977
978        final int top = -maxOverScrollY;
979        final int bottom = maxOverScrollY + scrollRangeY;
980
981        boolean clampedY = false;
982        if (newScrollY > bottom) {
983            newScrollY = bottom;
984            clampedY = true;
985        } else if (newScrollY < top) {
986            newScrollY = top;
987            clampedY = true;
988        }
989
990        onOverScrolled(0, newScrollY, false, clampedY);
991
992        return clampedY;
993    }
994
995    /**
996     * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
997     * overscroll effect based on numPixels. By default this will also cancel animations on the
998     * same overScroll edge.
999     *
1000     * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1001     *                  the rubber-banding logic.
1002     * @param onTop Should the effect be applied on top of the scroller.
1003     * @param animate Should an animation be performed.
1004     */
1005    public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
1006        setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
1007    }
1008
1009    /**
1010     * Set the effective overScroll amount which will be directly reflected in the layout.
1011     * By default this will also cancel animations on the same overScroll edge.
1012     *
1013     * @param amount The amount to overScroll by.
1014     * @param onTop Should the effect be applied on top of the scroller.
1015     * @param animate Should an animation be performed.
1016     */
1017    public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1018        setOverScrollAmount(amount, onTop, animate, true);
1019    }
1020
1021    /**
1022     * Set the effective overScroll amount which will be directly reflected in the layout.
1023     *
1024     * @param amount The amount to overScroll by.
1025     * @param onTop Should the effect be applied on top of the scroller.
1026     * @param animate Should an animation be performed.
1027     * @param cancelAnimators Should running animations be cancelled.
1028     */
1029    public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1030            boolean cancelAnimators) {
1031        setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1032    }
1033
1034    /**
1035     * Set the effective overScroll amount which will be directly reflected in the layout.
1036     *
1037     * @param amount The amount to overScroll by.
1038     * @param onTop Should the effect be applied on top of the scroller.
1039     * @param animate Should an animation be performed.
1040     * @param cancelAnimators Should running animations be cancelled.
1041     * @param isRubberbanded The value which will be passed to
1042     *                     {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1043     */
1044    public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1045            boolean cancelAnimators, boolean isRubberbanded) {
1046        if (cancelAnimators) {
1047            mStateAnimator.cancelOverScrollAnimators(onTop);
1048        }
1049        setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
1050    }
1051
1052    private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1053            boolean isRubberbanded) {
1054        amount = Math.max(0, amount);
1055        if (animate) {
1056            mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
1057        } else {
1058            setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
1059            mAmbientState.setOverScrollAmount(amount, onTop);
1060            if (onTop) {
1061                notifyOverscrollTopListener(amount, isRubberbanded);
1062            }
1063            requestChildrenUpdate();
1064        }
1065    }
1066
1067    private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
1068        mExpandHelper.onlyObserveMovements(amount > 1.0f);
1069        if (mDontReportNextOverScroll) {
1070            mDontReportNextOverScroll = false;
1071            return;
1072        }
1073        if (mOverscrollTopChangedListener != null) {
1074            mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
1075        }
1076    }
1077
1078    public void setOverscrollTopChangedListener(
1079            OnOverscrollTopChangedListener overscrollTopChangedListener) {
1080        mOverscrollTopChangedListener = overscrollTopChangedListener;
1081    }
1082
1083    public float getCurrentOverScrollAmount(boolean top) {
1084        return mAmbientState.getOverScrollAmount(top);
1085    }
1086
1087    public float getCurrentOverScrolledPixels(boolean top) {
1088        return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1089    }
1090
1091    private void setOverScrolledPixels(float amount, boolean onTop) {
1092        if (onTop) {
1093            mOverScrolledTopPixels = amount;
1094        } else {
1095            mOverScrolledBottomPixels = amount;
1096        }
1097    }
1098
1099    private void customScrollTo(int y) {
1100        mOwnScrollY = y;
1101        updateChildren();
1102    }
1103
1104    @Override
1105    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
1106        // Treat animating scrolls differently; see #computeScroll() for why.
1107        if (!mScroller.isFinished()) {
1108            final int oldX = mScrollX;
1109            final int oldY = mOwnScrollY;
1110            mScrollX = scrollX;
1111            mOwnScrollY = scrollY;
1112            if (clampedY) {
1113                springBack();
1114            } else {
1115                onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1116                invalidateParentIfNeeded();
1117                updateChildren();
1118                float overScrollTop = getCurrentOverScrollAmount(true);
1119                if (mOwnScrollY < 0) {
1120                    notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
1121                } else {
1122                    notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
1123                }
1124            }
1125        } else {
1126            customScrollTo(scrollY);
1127            scrollTo(scrollX, mScrollY);
1128        }
1129    }
1130
1131    private void springBack() {
1132        int scrollRange = getScrollRange();
1133        boolean overScrolledTop = mOwnScrollY <= 0;
1134        boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1135        if (overScrolledTop || overScrolledBottom) {
1136            boolean onTop;
1137            float newAmount;
1138            if (overScrolledTop) {
1139                onTop = true;
1140                newAmount = -mOwnScrollY;
1141                mOwnScrollY = 0;
1142                mDontReportNextOverScroll = true;
1143            } else {
1144                onTop = false;
1145                newAmount = mOwnScrollY - scrollRange;
1146                mOwnScrollY = scrollRange;
1147            }
1148            setOverScrollAmount(newAmount, onTop, false);
1149            setOverScrollAmount(0.0f, onTop, true);
1150            mScroller.forceFinished(true);
1151        }
1152    }
1153
1154    private int getScrollRange() {
1155        int scrollRange = 0;
1156        ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
1157        if (firstChild != null) {
1158            int contentHeight = getContentHeight();
1159            int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
1160            scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1161                    + mBottomStackSlowDownHeight);
1162            if (scrollRange > 0) {
1163                View lastChild = getLastChildNotGone();
1164                // We want to at least be able collapse the first item and not ending in a weird
1165                // end state.
1166                scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight - mCollapsedSize);
1167            }
1168        }
1169        return scrollRange;
1170    }
1171
1172    /**
1173     * @return the first child which has visibility unequal to GONE
1174     */
1175    private View getFirstChildNotGone() {
1176        int childCount = getChildCount();
1177        for (int i = 0; i < childCount; i++) {
1178            View child = getChildAt(i);
1179            if (child.getVisibility() != View.GONE) {
1180                return child;
1181            }
1182        }
1183        return null;
1184    }
1185
1186    /**
1187     * @return The first child which has visibility unequal to GONE which is currently below the
1188     *         given translationY or equal to it.
1189     */
1190    private View getFirstChildBelowTranlsationY(float translationY) {
1191        int childCount = getChildCount();
1192        for (int i = 0; i < childCount; i++) {
1193            View child = getChildAt(i);
1194            if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1195                return child;
1196            }
1197        }
1198        return null;
1199    }
1200
1201    /**
1202     * @return the last child which has visibility unequal to GONE
1203     */
1204    public View getLastChildNotGone() {
1205        int childCount = getChildCount();
1206        for (int i = childCount - 1; i >= 0; i--) {
1207            View child = getChildAt(i);
1208            if (child.getVisibility() != View.GONE) {
1209                return child;
1210            }
1211        }
1212        return null;
1213    }
1214
1215    /**
1216     * @return the number of children which have visibility unequal to GONE
1217     */
1218    public int getNotGoneChildCount() {
1219        int childCount = getChildCount();
1220        int count = 0;
1221        for (int i = 0; i < childCount; i++) {
1222            View child = getChildAt(i);
1223            if (child.getVisibility() != View.GONE) {
1224                count++;
1225            }
1226        }
1227        if (mDismissView.willBeGone()) {
1228            count--;
1229        }
1230        if (mEmptyShadeView.willBeGone()) {
1231            count--;
1232        }
1233        return count;
1234    }
1235
1236    private int getMaxExpandHeight(View view) {
1237        if (view instanceof ExpandableNotificationRow) {
1238            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1239            return row.getIntrinsicHeight();
1240        }
1241        return view.getHeight();
1242    }
1243
1244    public int getContentHeight() {
1245        return mContentHeight;
1246    }
1247
1248    private void updateContentHeight() {
1249        int height = 0;
1250        for (int i = 0; i < getChildCount(); i++) {
1251            View child = getChildAt(i);
1252            if (child.getVisibility() != View.GONE) {
1253                if (height != 0) {
1254                    // add the padding before this element
1255                    height += mPaddingBetweenElements;
1256                }
1257                if (child instanceof ExpandableNotificationRow) {
1258                    ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1259                    height += row.getIntrinsicHeight();
1260                } else if (child instanceof ExpandableView) {
1261                    ExpandableView expandableView = (ExpandableView) child;
1262                    height += expandableView.getActualHeight();
1263                }
1264            }
1265        }
1266        mContentHeight = height + mTopPadding;
1267    }
1268
1269    /**
1270     * Fling the scroll view
1271     *
1272     * @param velocityY The initial velocity in the Y direction. Positive
1273     *                  numbers mean that the finger/cursor is moving down the screen,
1274     *                  which means we want to scroll towards the top.
1275     */
1276    private void fling(int velocityY) {
1277        if (getChildCount() > 0) {
1278            int scrollRange = getScrollRange();
1279
1280            float topAmount = getCurrentOverScrollAmount(true);
1281            float bottomAmount = getCurrentOverScrollAmount(false);
1282            if (velocityY < 0 && topAmount > 0) {
1283                mOwnScrollY -= (int) topAmount;
1284                mDontReportNextOverScroll = true;
1285                setOverScrollAmount(0, true, false);
1286                mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
1287                        * mOverflingDistance + topAmount;
1288            } else if (velocityY > 0 && bottomAmount > 0) {
1289                mOwnScrollY += bottomAmount;
1290                setOverScrollAmount(0, false, false);
1291                mMaxOverScroll = Math.abs(velocityY) / 1000f
1292                        * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1293                        +  bottomAmount;
1294            } else {
1295                // it will be set once we reach the boundary
1296                mMaxOverScroll = 0.0f;
1297            }
1298            mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
1299                    Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
1300
1301            postInvalidateOnAnimation();
1302        }
1303    }
1304
1305    /**
1306     * @return Whether a fling performed on the top overscroll edge lead to the expanded
1307     * overScroll view (i.e QS).
1308     */
1309    private boolean shouldOverScrollFling(int initialVelocity) {
1310        float topOverScroll = getCurrentOverScrollAmount(true);
1311        return mScrolledToTopOnFirstDown
1312                && !mExpandedInThisMotion
1313                && topOverScroll > mMinTopOverScrollToEscape
1314                && initialVelocity > 0;
1315    }
1316
1317    public void updateTopPadding(float qsHeight, int scrollY, boolean animate) {
1318        float start = qsHeight - scrollY + mNotificationTopPadding;
1319        float stackHeight = getHeight() - start;
1320        int minStackHeight = getMinStackHeight();
1321        if (stackHeight <= minStackHeight) {
1322            float overflow = minStackHeight - stackHeight;
1323            stackHeight = minStackHeight;
1324            start = getHeight() - stackHeight;
1325            setTranslationY(overflow);
1326            mTopPaddingOverflow = overflow;
1327        } else {
1328            setTranslationY(0);
1329            mTopPaddingOverflow = 0;
1330        }
1331        setTopPadding(clampPadding((int) start), animate);
1332    }
1333
1334    public int getNotificationTopPadding() {
1335        return mNotificationTopPadding;
1336    }
1337
1338    public int getMinStackHeight() {
1339        return mCollapsedSize + mBottomStackPeekSize + mCollapseSecondCardPadding;
1340    }
1341
1342    public float getTopPaddingOverflow() {
1343        return mTopPaddingOverflow;
1344    }
1345
1346    public int getPeekHeight() {
1347        return mIntrinsicPadding + mCollapsedSize + mBottomStackPeekSize
1348                + mCollapseSecondCardPadding;
1349    }
1350
1351    private int clampPadding(int desiredPadding) {
1352        return Math.max(desiredPadding, mIntrinsicPadding);
1353    }
1354
1355    private float getRubberBandFactor(boolean onTop) {
1356        if (!onTop) {
1357            return RUBBER_BAND_FACTOR_NORMAL;
1358        }
1359        if (mExpandedInThisMotion) {
1360            return RUBBER_BAND_FACTOR_AFTER_EXPAND;
1361        } else if (mIsExpansionChanging) {
1362            return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1363        } else if (mScrolledToTopOnFirstDown) {
1364            return 1.0f;
1365        }
1366        return RUBBER_BAND_FACTOR_NORMAL;
1367    }
1368
1369    /**
1370     * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1371     * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1372     * overscroll view (e.g. expand QS).
1373     */
1374    private boolean isRubberbanded(boolean onTop) {
1375        return !onTop || mExpandedInThisMotion || mIsExpansionChanging
1376                || !mScrolledToTopOnFirstDown;
1377    }
1378
1379    private void endDrag() {
1380        setIsBeingDragged(false);
1381
1382        recycleVelocityTracker();
1383
1384        if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1385            setOverScrollAmount(0, true /* onTop */, true /* animate */);
1386        }
1387        if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1388            setOverScrollAmount(0, false /* onTop */, true /* animate */);
1389        }
1390    }
1391
1392    private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1393        ev.offsetLocation(sourceView.getX(), sourceView.getY());
1394        ev.offsetLocation(-targetView.getX(), -targetView.getY());
1395    }
1396
1397    @Override
1398    public boolean onInterceptTouchEvent(MotionEvent ev) {
1399        if (mInterceptDelegateEnabled) {
1400            transformTouchEvent(ev, this, mScrollView);
1401            if (mScrollView.onInterceptTouchEvent(ev)) {
1402                mDelegateToScrollView = true;
1403                removeLongPressCallback();
1404                return true;
1405            }
1406            transformTouchEvent(ev, mScrollView, this);
1407        }
1408        initDownStates(ev);
1409        boolean expandWantsIt = false;
1410        if (!mSwipingInProgress && !mOnlyScrollingInThisMotion && isScrollingEnabled()) {
1411            expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1412        }
1413        boolean scrollWantsIt = false;
1414        if (!mSwipingInProgress && !mExpandingNotification) {
1415            scrollWantsIt = onInterceptTouchEventScroll(ev);
1416        }
1417        boolean swipeWantsIt = false;
1418        if (!mIsBeingDragged
1419                && !mExpandingNotification
1420                && !mExpandedInThisMotion
1421                && !mOnlyScrollingInThisMotion) {
1422            swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1423        }
1424        return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1425    }
1426
1427    private void initDownStates(MotionEvent ev) {
1428        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1429            mExpandedInThisMotion = false;
1430            mOnlyScrollingInThisMotion = !mScroller.isFinished();
1431            mDisallowScrollingInThisMotion = false;
1432        }
1433    }
1434
1435    @Override
1436    protected void onViewRemoved(View child) {
1437        super.onViewRemoved(child);
1438        mStackScrollAlgorithm.notifyChildrenChanged(this);
1439        if (mChangePositionInProgress) {
1440            // This is only a position change, don't do anything special
1441            return;
1442        }
1443        ((ExpandableView) child).setOnHeightChangedListener(null);
1444        mCurrentStackScrollState.removeViewStateForView(child);
1445        updateScrollStateForRemovedChild(child);
1446        boolean animationGenerated = generateRemoveAnimation(child);
1447        if (animationGenerated && !mSwipedOutViews.contains(child)) {
1448            // Add this view to an overlay in order to ensure that it will still be temporary
1449            // drawn when removed
1450            getOverlay().add(child);
1451        }
1452    }
1453
1454    /**
1455     * Generate a remove animation for a child view.
1456     *
1457     * @param child The view to generate the remove animation for.
1458     * @return Whether an animation was generated.
1459     */
1460    private boolean generateRemoveAnimation(View child) {
1461        if (mIsExpanded && mAnimationsEnabled) {
1462            if (!mChildrenToAddAnimated.contains(child)) {
1463                // Generate Animations
1464                mChildrenToRemoveAnimated.add(child);
1465                mNeedsAnimation = true;
1466                return true;
1467            } else {
1468                mChildrenToAddAnimated.remove(child);
1469                mFromMoreCardAdditions.remove(child);
1470                return false;
1471            }
1472        }
1473        return false;
1474    }
1475
1476    /**
1477     * Updates the scroll position when a child was removed
1478     *
1479     * @param removedChild the removed child
1480     */
1481    private void updateScrollStateForRemovedChild(View removedChild) {
1482        int startingPosition = getPositionInLinearLayout(removedChild);
1483        int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
1484        int endPosition = startingPosition + childHeight;
1485        if (endPosition <= mOwnScrollY) {
1486            // This child is fully scrolled of the top, so we have to deduct its height from the
1487            // scrollPosition
1488            mOwnScrollY -= childHeight;
1489        } else if (startingPosition < mOwnScrollY) {
1490            // This child is currently being scrolled into, set the scroll position to the start of
1491            // this child
1492            mOwnScrollY = startingPosition;
1493        }
1494    }
1495
1496    private int getIntrinsicHeight(View view) {
1497        if (view instanceof ExpandableView) {
1498            ExpandableView expandableView = (ExpandableView) view;
1499            return expandableView.getIntrinsicHeight();
1500        }
1501        return view.getHeight();
1502    }
1503
1504    private int getPositionInLinearLayout(View requestedChild) {
1505        int position = 0;
1506        for (int i = 0; i < getChildCount(); i++) {
1507            View child = getChildAt(i);
1508            if (child == requestedChild) {
1509                return position;
1510            }
1511            if (child.getVisibility() != View.GONE) {
1512                position += child.getHeight();
1513                if (i < getChildCount()-1) {
1514                    position += mPaddingBetweenElements;
1515                }
1516            }
1517        }
1518        return 0;
1519    }
1520
1521    @Override
1522    protected void onViewAdded(View child) {
1523        super.onViewAdded(child);
1524        mStackScrollAlgorithm.notifyChildrenChanged(this);
1525        ((ExpandableView) child).setOnHeightChangedListener(this);
1526        generateAddAnimation(child, false /* fromMoreCard */);
1527    }
1528
1529    public void setAnimationsEnabled(boolean animationsEnabled) {
1530        mAnimationsEnabled = animationsEnabled;
1531    }
1532
1533    public boolean isAddOrRemoveAnimationPending() {
1534        return mNeedsAnimation
1535                && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
1536    }
1537    /**
1538     * Generate an animation for an added child view.
1539     *
1540     * @param child The view to be added.
1541     * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
1542     */
1543    public void generateAddAnimation(View child, boolean fromMoreCard) {
1544        if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
1545            // Generate Animations
1546            mChildrenToAddAnimated.add(child);
1547            if (fromMoreCard) {
1548                mFromMoreCardAdditions.add(child);
1549            }
1550            mNeedsAnimation = true;
1551        }
1552    }
1553
1554    /**
1555     * Change the position of child to a new location
1556     *
1557     * @param child the view to change the position for
1558     * @param newIndex the new index
1559     */
1560    public void changeViewPosition(View child, int newIndex) {
1561        int currentIndex = indexOfChild(child);
1562        if (child != null && child.getParent() == this && currentIndex != newIndex) {
1563            mChangePositionInProgress = true;
1564            removeView(child);
1565            addView(child, newIndex);
1566            mChangePositionInProgress = false;
1567            if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
1568                mChildrenChangingPositions.add(child);
1569                mNeedsAnimation = true;
1570            }
1571        }
1572    }
1573
1574    private void startAnimationToState() {
1575        if (mNeedsAnimation) {
1576            generateChildHierarchyEvents();
1577            mNeedsAnimation = false;
1578        }
1579        if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
1580            mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
1581                    mGoToFullShadeDelay);
1582            mAnimationEvents.clear();
1583        } else {
1584            applyCurrentState();
1585        }
1586        mGoToFullShadeDelay = 0;
1587    }
1588
1589    private void generateChildHierarchyEvents() {
1590        generateChildRemovalEvents();
1591        generateChildAdditionEvents();
1592        generatePositionChangeEvents();
1593        generateSnapBackEvents();
1594        generateDragEvents();
1595        generateTopPaddingEvent();
1596        generateActivateEvent();
1597        generateDimmedEvent();
1598        generateHideSensitiveEvent();
1599        generateDarkEvent();
1600        generateGoToFullShadeEvent();
1601        mNeedsAnimation = false;
1602    }
1603
1604    private void generateSnapBackEvents() {
1605        for (View child : mSnappedBackChildren) {
1606            mAnimationEvents.add(new AnimationEvent(child,
1607                    AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
1608        }
1609        mSnappedBackChildren.clear();
1610    }
1611
1612    private void generateDragEvents() {
1613        for (View child : mDragAnimPendingChildren) {
1614            mAnimationEvents.add(new AnimationEvent(child,
1615                    AnimationEvent.ANIMATION_TYPE_START_DRAG));
1616        }
1617        mDragAnimPendingChildren.clear();
1618    }
1619
1620    private void generateChildRemovalEvents() {
1621        for (View child : mChildrenToRemoveAnimated) {
1622            boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1623            int animationType = childWasSwipedOut
1624                    ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1625                    : AnimationEvent.ANIMATION_TYPE_REMOVE;
1626            AnimationEvent event = new AnimationEvent(child, animationType);
1627
1628            // we need to know the view after this one
1629            event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
1630            mAnimationEvents.add(event);
1631        }
1632        mSwipedOutViews.clear();
1633        mChildrenToRemoveAnimated.clear();
1634    }
1635
1636    private void generatePositionChangeEvents() {
1637        for (View child : mChildrenChangingPositions) {
1638            mAnimationEvents.add(new AnimationEvent(child,
1639                    AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
1640        }
1641        mChildrenChangingPositions.clear();
1642    }
1643
1644    private void generateChildAdditionEvents() {
1645        for (View child : mChildrenToAddAnimated) {
1646            if (mFromMoreCardAdditions.contains(child)) {
1647                mAnimationEvents.add(new AnimationEvent(child,
1648                        AnimationEvent.ANIMATION_TYPE_ADD,
1649                        StackStateAnimator.ANIMATION_DURATION_STANDARD));
1650            } else {
1651                mAnimationEvents.add(new AnimationEvent(child,
1652                        AnimationEvent.ANIMATION_TYPE_ADD));
1653            }
1654        }
1655        mChildrenToAddAnimated.clear();
1656        mFromMoreCardAdditions.clear();
1657    }
1658
1659    private void generateTopPaddingEvent() {
1660        if (mTopPaddingNeedsAnimation) {
1661            mAnimationEvents.add(
1662                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
1663        }
1664        mTopPaddingNeedsAnimation = false;
1665    }
1666
1667    private void generateActivateEvent() {
1668        if (mActivateNeedsAnimation) {
1669            mAnimationEvents.add(
1670                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
1671        }
1672        mActivateNeedsAnimation = false;
1673    }
1674
1675    private void generateDimmedEvent() {
1676        if (mDimmedNeedsAnimation) {
1677            mAnimationEvents.add(
1678                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
1679        }
1680        mDimmedNeedsAnimation = false;
1681    }
1682
1683    private void generateHideSensitiveEvent() {
1684        if (mHideSensitiveNeedsAnimation) {
1685            mAnimationEvents.add(
1686                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
1687        }
1688        mHideSensitiveNeedsAnimation = false;
1689    }
1690
1691    private void generateDarkEvent() {
1692        if (mDarkNeedsAnimation) {
1693            mAnimationEvents.add(
1694                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK));
1695        }
1696        mDarkNeedsAnimation = false;
1697    }
1698
1699    private void generateGoToFullShadeEvent() {
1700        if (mGoToFullShadeNeedsAnimation) {
1701            mAnimationEvents.add(
1702                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
1703        }
1704        mGoToFullShadeNeedsAnimation = false;
1705    }
1706
1707    private boolean onInterceptTouchEventScroll(MotionEvent ev) {
1708        if (!isScrollingEnabled()) {
1709            return false;
1710        }
1711        /*
1712         * This method JUST determines whether we want to intercept the motion.
1713         * If we return true, onMotionEvent will be called and we do the actual
1714         * scrolling there.
1715         */
1716
1717        /*
1718        * Shortcut the most recurring case: the user is in the dragging
1719        * state and he is moving his finger.  We want to intercept this
1720        * motion.
1721        */
1722        final int action = ev.getAction();
1723        if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
1724            return true;
1725        }
1726
1727        switch (action & MotionEvent.ACTION_MASK) {
1728            case MotionEvent.ACTION_MOVE: {
1729                /*
1730                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
1731                 * whether the user has moved far enough from his original down touch.
1732                 */
1733
1734                /*
1735                * Locally do absolute value. mLastMotionY is set to the y value
1736                * of the down event.
1737                */
1738                final int activePointerId = mActivePointerId;
1739                if (activePointerId == INVALID_POINTER) {
1740                    // If we don't have a valid id, the touch down wasn't on content.
1741                    break;
1742                }
1743
1744                final int pointerIndex = ev.findPointerIndex(activePointerId);
1745                if (pointerIndex == -1) {
1746                    Log.e(TAG, "Invalid pointerId=" + activePointerId
1747                            + " in onInterceptTouchEvent");
1748                    break;
1749                }
1750
1751                final int y = (int) ev.getY(pointerIndex);
1752                final int x = (int) ev.getX(pointerIndex);
1753                final int yDiff = Math.abs(y - mLastMotionY);
1754                final int xDiff = Math.abs(x - mDownX);
1755                if (yDiff > mTouchSlop && yDiff > xDiff) {
1756                    setIsBeingDragged(true);
1757                    mLastMotionY = y;
1758                    mDownX = x;
1759                    initVelocityTrackerIfNotExists();
1760                    mVelocityTracker.addMovement(ev);
1761                }
1762                break;
1763            }
1764
1765            case MotionEvent.ACTION_DOWN: {
1766                final int y = (int) ev.getY();
1767                if (getChildAtPosition(ev.getX(), y) == null) {
1768                    setIsBeingDragged(false);
1769                    recycleVelocityTracker();
1770                    break;
1771                }
1772
1773                /*
1774                 * Remember location of down touch.
1775                 * ACTION_DOWN always refers to pointer index 0.
1776                 */
1777                mLastMotionY = y;
1778                mDownX = (int) ev.getX();
1779                mActivePointerId = ev.getPointerId(0);
1780                mScrolledToTopOnFirstDown = isScrolledToTop();
1781
1782                initOrResetVelocityTracker();
1783                mVelocityTracker.addMovement(ev);
1784                /*
1785                * If being flinged and user touches the screen, initiate drag;
1786                * otherwise don't.  mScroller.isFinished should be false when
1787                * being flinged.
1788                */
1789                boolean isBeingDragged = !mScroller.isFinished();
1790                setIsBeingDragged(isBeingDragged);
1791                break;
1792            }
1793
1794            case MotionEvent.ACTION_CANCEL:
1795            case MotionEvent.ACTION_UP:
1796                /* Release the drag */
1797                setIsBeingDragged(false);
1798                mActivePointerId = INVALID_POINTER;
1799                recycleVelocityTracker();
1800                if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1801                    postInvalidateOnAnimation();
1802                }
1803                break;
1804            case MotionEvent.ACTION_POINTER_UP:
1805                onSecondaryPointerUp(ev);
1806                break;
1807        }
1808
1809        /*
1810        * The only time we want to intercept motion events is if we are in the
1811        * drag mode.
1812        */
1813        return mIsBeingDragged;
1814    }
1815
1816    /**
1817     * @return Whether the specified motion event is actually happening over the content.
1818     */
1819    private boolean isInContentBounds(MotionEvent event) {
1820        return event.getY() < getHeight() - getEmptyBottomMargin();
1821    }
1822
1823    private void setIsBeingDragged(boolean isDragged) {
1824        mIsBeingDragged = isDragged;
1825        if (isDragged) {
1826            requestDisallowInterceptTouchEvent(true);
1827            removeLongPressCallback();
1828        }
1829    }
1830
1831    @Override
1832    public void onWindowFocusChanged(boolean hasWindowFocus) {
1833        super.onWindowFocusChanged(hasWindowFocus);
1834        if (!hasWindowFocus) {
1835            removeLongPressCallback();
1836        }
1837    }
1838
1839    public void removeLongPressCallback() {
1840        mSwipeHelper.removeLongPressCallback();
1841    }
1842
1843    @Override
1844    public boolean isScrolledToTop() {
1845        return mOwnScrollY == 0;
1846    }
1847
1848    @Override
1849    public boolean isScrolledToBottom() {
1850        return mOwnScrollY >= getScrollRange();
1851    }
1852
1853    @Override
1854    public View getHostView() {
1855        return this;
1856    }
1857
1858    public int getEmptyBottomMargin() {
1859        int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
1860        if (needsHeightAdaption()) {
1861            emptyMargin -= mBottomStackSlowDownHeight;
1862        } else {
1863            emptyMargin -= mCollapseSecondCardPadding;
1864        }
1865        return Math.max(emptyMargin, 0);
1866    }
1867
1868    public void onExpansionStarted() {
1869        mIsExpansionChanging = true;
1870        mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
1871    }
1872
1873    public void onExpansionStopped() {
1874        mIsExpansionChanging = false;
1875        mStackScrollAlgorithm.onExpansionStopped();
1876        if (!mIsExpanded) {
1877            mOwnScrollY = 0;
1878        }
1879    }
1880
1881    private void setIsExpanded(boolean isExpanded) {
1882        mIsExpanded = isExpanded;
1883        mStackScrollAlgorithm.setIsExpanded(isExpanded);
1884    }
1885
1886    @Override
1887    public void onHeightChanged(ExpandableView view) {
1888        updateContentHeight();
1889        updateScrollPositionOnExpandInBottom(view);
1890        clampScrollPosition();
1891        notifyHeightChangeListener(view);
1892        requestChildrenUpdate();
1893    }
1894
1895    private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
1896        if (view instanceof ExpandableNotificationRow) {
1897            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
1898            if (row.isUserLocked()) {
1899                // We are actually expanding this view
1900                float endPosition = row.getTranslationY() + row.getActualHeight();
1901                int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
1902                        mBottomStackSlowDownHeight;
1903                if (endPosition > stackEnd) {
1904                    mOwnScrollY += endPosition - stackEnd;
1905                    mDisallowScrollingInThisMotion = true;
1906                }
1907            }
1908        }
1909    }
1910
1911    public void setOnHeightChangedListener(
1912            ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
1913        this.mOnHeightChangedListener = mOnHeightChangedListener;
1914    }
1915
1916    public void onChildAnimationFinished() {
1917        requestChildrenUpdate();
1918    }
1919
1920    /**
1921     * See {@link AmbientState#setDimmed}.
1922     */
1923    public void setDimmed(boolean dimmed, boolean animate) {
1924        mStackScrollAlgorithm.setDimmed(dimmed);
1925        mAmbientState.setDimmed(dimmed);
1926        updatePadding(dimmed);
1927        if (animate && mAnimationsEnabled) {
1928            mDimmedNeedsAnimation = true;
1929            mNeedsAnimation =  true;
1930        }
1931        requestChildrenUpdate();
1932    }
1933
1934    public void setHideSensitive(boolean hideSensitive, boolean animate) {
1935        if (hideSensitive != mAmbientState.isHideSensitive()) {
1936            int childCount = getChildCount();
1937            for (int i = 0; i < childCount; i++) {
1938                ExpandableView v = (ExpandableView) getChildAt(i);
1939                v.setHideSensitiveForIntrinsicHeight(hideSensitive);
1940            }
1941            mAmbientState.setHideSensitive(hideSensitive);
1942            if (animate && mAnimationsEnabled) {
1943                mHideSensitiveNeedsAnimation = true;
1944                mNeedsAnimation =  true;
1945            }
1946            requestChildrenUpdate();
1947        }
1948    }
1949
1950    /**
1951     * See {@link AmbientState#setActivatedChild}.
1952     */
1953    public void setActivatedChild(ActivatableNotificationView activatedChild) {
1954        mAmbientState.setActivatedChild(activatedChild);
1955        if (mAnimationsEnabled) {
1956            mActivateNeedsAnimation = true;
1957            mNeedsAnimation =  true;
1958        }
1959        requestChildrenUpdate();
1960    }
1961
1962    public ActivatableNotificationView getActivatedChild() {
1963        return mAmbientState.getActivatedChild();
1964    }
1965
1966    private void applyCurrentState() {
1967        mCurrentStackScrollState.apply();
1968        if (mListener != null) {
1969            mListener.onChildLocationsChanged(this);
1970        }
1971    }
1972
1973    public void setSpeedBumpView(SpeedBumpView speedBumpView) {
1974        mSpeedBumpView = speedBumpView;
1975        addView(speedBumpView);
1976    }
1977
1978    private void updateSpeedBump(boolean visible) {
1979        boolean notGoneBefore = mSpeedBumpView.getVisibility() != GONE;
1980        if (visible != notGoneBefore) {
1981            int newVisibility = visible ? VISIBLE : GONE;
1982            mSpeedBumpView.setVisibility(newVisibility);
1983            if (visible) {
1984                // Make invisible to ensure that the appear animation is played.
1985                mSpeedBumpView.setInvisible();
1986            } else {
1987                // TODO: This doesn't really work, because the view is already set to GONE above.
1988                generateRemoveAnimation(mSpeedBumpView);
1989            }
1990        }
1991    }
1992
1993    public void goToFullShade(long delay) {
1994        updateSpeedBump(true /* visibility */);
1995        mDismissView.setInvisible();
1996        mEmptyShadeView.setInvisible();
1997        mGoToFullShadeNeedsAnimation = true;
1998        mGoToFullShadeDelay = delay;
1999        mNeedsAnimation =  true;
2000        requestChildrenUpdate();
2001    }
2002
2003    public void cancelExpandHelper() {
2004        mExpandHelper.cancel();
2005    }
2006
2007    public void setIntrinsicPadding(int intrinsicPadding) {
2008        mIntrinsicPadding = intrinsicPadding;
2009    }
2010
2011    public int getIntrinsicPadding() {
2012        return mIntrinsicPadding;
2013    }
2014
2015    /**
2016     * @return the y position of the first notification
2017     */
2018    public float getNotificationsTopY() {
2019        return mTopPadding + getTranslationY();
2020    }
2021
2022    @Override
2023    public boolean shouldDelayChildPressedState() {
2024        return true;
2025    }
2026
2027    public void setScrimAlpha(float progress) {
2028        if (progress != mAmbientState.getScrimAmount()) {
2029            mAmbientState.setScrimAmount(progress);
2030            requestChildrenUpdate();
2031        }
2032    }
2033
2034    /**
2035     * See {@link AmbientState#setDark}.
2036     */
2037    public void setDark(boolean dark, boolean animate) {
2038        mAmbientState.setDark(dark);
2039        if (animate && mAnimationsEnabled) {
2040            mDarkNeedsAnimation = true;
2041            mNeedsAnimation =  true;
2042        }
2043        requestChildrenUpdate();
2044    }
2045
2046    public void setDismissView(DismissView dismissView) {
2047        mDismissView = dismissView;
2048        addView(mDismissView);
2049    }
2050
2051    public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
2052        mEmptyShadeView = emptyShadeView;
2053        addView(mEmptyShadeView);
2054    }
2055
2056    public void updateEmptyShadeView(boolean visible) {
2057        int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
2058        int newVisibility = visible ? VISIBLE : GONE;
2059        if (oldVisibility != newVisibility) {
2060            if (oldVisibility == GONE) {
2061                if (mEmptyShadeView.willBeGone()) {
2062                    mEmptyShadeView.cancelAnimation();
2063                } else {
2064                    mEmptyShadeView.setInvisible();
2065                    mEmptyShadeView.setVisibility(newVisibility);
2066                }
2067                mEmptyShadeView.setWillBeGone(false);
2068                updateContentHeight();
2069            } else {
2070                mEmptyShadeView.setWillBeGone(true);
2071                mEmptyShadeView.performVisibilityAnimation(false, new Runnable() {
2072                    @Override
2073                    public void run() {
2074                        mEmptyShadeView.setVisibility(GONE);
2075                        mEmptyShadeView.setWillBeGone(false);
2076                        updateContentHeight();
2077                    }
2078                });
2079            }
2080        }
2081    }
2082
2083    public void updateDismissView(boolean visible) {
2084        int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
2085        int newVisibility = visible ? VISIBLE : GONE;
2086        if (oldVisibility != newVisibility) {
2087            if (oldVisibility == GONE) {
2088                if (mDismissView.willBeGone()) {
2089                    mDismissView.cancelAnimation();
2090                } else {
2091                    mDismissView.setInvisible();
2092                    mDismissView.setVisibility(newVisibility);
2093                }
2094                mDismissView.setWillBeGone(false);
2095                updateContentHeight();
2096            } else {
2097                mDismissView.setWillBeGone(true);
2098                mDismissView.performVisibilityAnimation(false, new Runnable() {
2099                    @Override
2100                    public void run() {
2101                        mDismissView.setVisibility(GONE);
2102                        mDismissView.setWillBeGone(false);
2103                        updateContentHeight();
2104                    }
2105                });
2106            }
2107        }
2108    }
2109
2110    public void setDismissAllInProgress(boolean dismissAllInProgress) {
2111        mDismissAllInProgress = dismissAllInProgress;
2112    }
2113
2114    public boolean isDismissViewNotGone() {
2115        return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
2116    }
2117
2118    public boolean isDismissViewVisible() {
2119        return mDismissView.isVisible();
2120    }
2121
2122    public int getDismissViewHeight() {
2123        return mDismissView.getHeight() + mPaddingBetweenElementsNormal;
2124    }
2125
2126    /**
2127     * A listener that is notified when some child locations might have changed.
2128     */
2129    public interface OnChildLocationsChangedListener {
2130        public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
2131    }
2132
2133    /**
2134     * A listener that gets notified when the overscroll at the top has changed.
2135     */
2136    public interface OnOverscrollTopChangedListener {
2137
2138        /**
2139         * Notifies a listener that the overscroll has changed.
2140         *
2141         * @param amount the amount of overscroll, in pixels
2142         * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
2143         *                     unrubberbanded motion to directly expand overscroll view (e.g expand
2144         *                     QS)
2145         */
2146        public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
2147
2148        /**
2149         * Notify a listener that the scroller wants to escape from the scrolling motion and
2150         * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
2151         *
2152         * @param velocity The velocity that the Scroller had when over flinging
2153         * @param open Should the fling open or close the overscroll view.
2154         */
2155        public void flingTopOverscroll(float velocity, boolean open);
2156    }
2157
2158    static class AnimationEvent {
2159
2160        static AnimationFilter[] FILTERS = new AnimationFilter[] {
2161
2162                // ANIMATION_TYPE_ADD
2163                new AnimationFilter()
2164                        .animateAlpha()
2165                        .animateHeight()
2166                        .animateTopInset()
2167                        .animateY()
2168                        .animateZ()
2169                        .hasDelays(),
2170
2171                // ANIMATION_TYPE_REMOVE
2172                new AnimationFilter()
2173                        .animateAlpha()
2174                        .animateHeight()
2175                        .animateTopInset()
2176                        .animateY()
2177                        .animateZ()
2178                        .hasDelays(),
2179
2180                // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2181                new AnimationFilter()
2182                        .animateAlpha()
2183                        .animateHeight()
2184                        .animateTopInset()
2185                        .animateY()
2186                        .animateZ()
2187                        .hasDelays(),
2188
2189                // ANIMATION_TYPE_TOP_PADDING_CHANGED
2190                new AnimationFilter()
2191                        .animateAlpha()
2192                        .animateHeight()
2193                        .animateTopInset()
2194                        .animateY()
2195                        .animateDimmed()
2196                        .animateScale()
2197                        .animateZ(),
2198
2199                // ANIMATION_TYPE_START_DRAG
2200                new AnimationFilter()
2201                        .animateAlpha(),
2202
2203                // ANIMATION_TYPE_SNAP_BACK
2204                new AnimationFilter()
2205                        .animateAlpha()
2206                        .animateHeight(),
2207
2208                // ANIMATION_TYPE_ACTIVATED_CHILD
2209                new AnimationFilter()
2210                        .animateScale()
2211                        .animateAlpha(),
2212
2213                // ANIMATION_TYPE_DIMMED
2214                new AnimationFilter()
2215                        .animateY()
2216                        .animateScale()
2217                        .animateDimmed(),
2218
2219                // ANIMATION_TYPE_CHANGE_POSITION
2220                new AnimationFilter()
2221                        .animateAlpha()
2222                        .animateHeight()
2223                        .animateTopInset()
2224                        .animateY()
2225                        .animateZ(),
2226
2227                // ANIMATION_TYPE_DARK
2228                new AnimationFilter()
2229                        .animateDark(),
2230
2231                // ANIMATION_TYPE_GO_TO_FULL_SHADE
2232                new AnimationFilter()
2233                        .animateAlpha()
2234                        .animateHeight()
2235                        .animateTopInset()
2236                        .animateY()
2237                        .animateDimmed()
2238                        .animateScale()
2239                        .animateZ()
2240                        .hasDelays(),
2241
2242                // ANIMATION_TYPE_HIDE_SENSITIVE
2243                new AnimationFilter()
2244                        .animateHideSensitive(),
2245        };
2246
2247        static int[] LENGTHS = new int[] {
2248
2249                // ANIMATION_TYPE_ADD
2250                StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
2251
2252                // ANIMATION_TYPE_REMOVE
2253                StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
2254
2255                // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2256                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2257
2258                // ANIMATION_TYPE_TOP_PADDING_CHANGED
2259                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2260
2261                // ANIMATION_TYPE_START_DRAG
2262                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2263
2264                // ANIMATION_TYPE_SNAP_BACK
2265                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2266
2267                // ANIMATION_TYPE_ACTIVATED_CHILD
2268                StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
2269
2270                // ANIMATION_TYPE_DIMMED
2271                StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
2272
2273                // ANIMATION_TYPE_CHANGE_POSITION
2274                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2275
2276                // ANIMATION_TYPE_DARK
2277                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2278
2279                // ANIMATION_TYPE_GO_TO_FULL_SHADE
2280                StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
2281
2282                // ANIMATION_TYPE_HIDE_SENSITIVE
2283                StackStateAnimator.ANIMATION_DURATION_STANDARD,
2284        };
2285
2286        static final int ANIMATION_TYPE_ADD = 0;
2287        static final int ANIMATION_TYPE_REMOVE = 1;
2288        static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
2289        static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
2290        static final int ANIMATION_TYPE_START_DRAG = 4;
2291        static final int ANIMATION_TYPE_SNAP_BACK = 5;
2292        static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
2293        static final int ANIMATION_TYPE_DIMMED = 7;
2294        static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
2295        static final int ANIMATION_TYPE_DARK = 9;
2296        static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
2297        static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
2298
2299        final long eventStartTime;
2300        final View changingView;
2301        final int animationType;
2302        final AnimationFilter filter;
2303        final long length;
2304        View viewAfterChangingView;
2305
2306        AnimationEvent(View view, int type) {
2307            this(view, type, LENGTHS[type]);
2308        }
2309
2310        AnimationEvent(View view, int type, long length) {
2311            eventStartTime = AnimationUtils.currentAnimationTimeMillis();
2312            changingView = view;
2313            animationType = type;
2314            filter = FILTERS[type];
2315            this.length = length;
2316        }
2317
2318        /**
2319         * Combines the length of several animation events into a single value.
2320         *
2321         * @param events The events of the lengths to combine.
2322         * @return The combined length. Depending on the event types, this might be the maximum of
2323         *         all events or the length of a specific event.
2324         */
2325        static long combineLength(ArrayList<AnimationEvent> events) {
2326            long length = 0;
2327            int size = events.size();
2328            for (int i = 0; i < size; i++) {
2329                AnimationEvent event = events.get(i);
2330                length = Math.max(length, event.length);
2331                if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
2332                    return event.length;
2333                }
2334            }
2335            return length;
2336        }
2337    }
2338
2339}
2340