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