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