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