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