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