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