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