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