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.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.PropertyValuesHolder;
     23 import android.animation.TimeAnimator;
     24 import android.animation.ValueAnimator;
     25 import android.animation.ValueAnimator.AnimatorUpdateListener;
     26 import android.annotation.FloatRange;
     27 import android.annotation.Nullable;
     28 import android.content.Context;
     29 import android.content.res.Configuration;
     30 import android.graphics.Canvas;
     31 import android.graphics.Color;
     32 import android.graphics.Paint;
     33 import android.graphics.PointF;
     34 import android.graphics.PorterDuff;
     35 import android.graphics.PorterDuffXfermode;
     36 import android.graphics.Rect;
     37 import android.os.Bundle;
     38 import android.os.Handler;
     39 import android.util.AttributeSet;
     40 import android.util.FloatProperty;
     41 import android.util.Log;
     42 import android.util.Pair;
     43 import android.util.Property;
     44 import android.view.MotionEvent;
     45 import android.view.VelocityTracker;
     46 import android.view.View;
     47 import android.view.ViewConfiguration;
     48 import android.view.ViewGroup;
     49 import android.view.ViewTreeObserver;
     50 import android.view.WindowInsets;
     51 import android.view.accessibility.AccessibilityEvent;
     52 import android.view.accessibility.AccessibilityNodeInfo;
     53 import android.view.animation.AnimationUtils;
     54 import android.view.animation.Interpolator;
     55 import android.widget.OverScroller;
     56 import android.widget.ScrollView;
     57 
     58 import com.android.internal.logging.MetricsLogger;
     59 import com.android.internal.logging.MetricsProto.MetricsEvent;
     60 import com.android.systemui.ExpandHelper;
     61 import com.android.systemui.Interpolators;
     62 import com.android.systemui.R;
     63 import com.android.systemui.SwipeHelper;
     64 import com.android.systemui.classifier.FalsingManager;
     65 import com.android.systemui.statusbar.ActivatableNotificationView;
     66 import com.android.systemui.statusbar.DismissView;
     67 import com.android.systemui.statusbar.EmptyShadeView;
     68 import com.android.systemui.statusbar.ExpandableNotificationRow;
     69 import com.android.systemui.statusbar.ExpandableView;
     70 import com.android.systemui.statusbar.NotificationGuts;
     71 import com.android.systemui.statusbar.NotificationOverflowContainer;
     72 import com.android.systemui.statusbar.NotificationSettingsIconRow;
     73 import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
     74 import com.android.systemui.statusbar.StackScrollerDecorView;
     75 import com.android.systemui.statusbar.StatusBarState;
     76 import com.android.systemui.statusbar.notification.FakeShadowView;
     77 import com.android.systemui.statusbar.notification.NotificationUtils;
     78 import com.android.systemui.statusbar.phone.NotificationGroupManager;
     79 import com.android.systemui.statusbar.phone.PhoneStatusBar;
     80 import com.android.systemui.statusbar.phone.ScrimController;
     81 import com.android.systemui.statusbar.policy.HeadsUpManager;
     82 import com.android.systemui.statusbar.policy.ScrollAdapter;
     83 
     84 import java.util.ArrayList;
     85 import java.util.Collections;
     86 import java.util.Comparator;
     87 import java.util.HashSet;
     88 
     89 /**
     90  * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
     91  */
     92 public class NotificationStackScrollLayout extends ViewGroup
     93         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
     94         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
     95         SettingsIconRowListener, ScrollContainer {
     96 
     97     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     98     private static final String TAG = "StackScroller";
     99     private static final boolean DEBUG = false;
    100     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
    101     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
    102     private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
    103     /**
    104      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
    105      */
    106     private static final int INVALID_POINTER = -1;
    107 
    108     private ExpandHelper mExpandHelper;
    109     private NotificationSwipeHelper mSwipeHelper;
    110     private boolean mSwipingInProgress;
    111     private int mCurrentStackHeight = Integer.MAX_VALUE;
    112     private final Paint mBackgroundPaint = new Paint();
    113 
    114     /**
    115      * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
    116      * externally from {@link #setStackHeight}
    117      */
    118     private float mLastSetStackHeight;
    119     private int mOwnScrollY;
    120     private int mMaxLayoutHeight;
    121 
    122     private VelocityTracker mVelocityTracker;
    123     private OverScroller mScroller;
    124     private Runnable mFinishScrollingCallback;
    125     private int mTouchSlop;
    126     private int mMinimumVelocity;
    127     private int mMaximumVelocity;
    128     private int mOverflingDistance;
    129     private float mMaxOverScroll;
    130     private boolean mIsBeingDragged;
    131     private int mLastMotionY;
    132     private int mDownX;
    133     private int mActivePointerId;
    134     private boolean mTouchIsClick;
    135     private float mInitialTouchX;
    136     private float mInitialTouchY;
    137 
    138     private Paint mDebugPaint;
    139     private int mContentHeight;
    140     private int mCollapsedSize;
    141     private int mBottomStackSlowDownHeight;
    142     private int mBottomStackPeekSize;
    143     private int mPaddingBetweenElements;
    144     private int mIncreasedPaddingBetweenElements;
    145     private int mTopPadding;
    146     private int mBottomInset = 0;
    147 
    148     /**
    149      * The algorithm which calculates the properties for our children
    150      */
    151     private final StackScrollAlgorithm mStackScrollAlgorithm;
    152 
    153     /**
    154      * The current State this Layout is in
    155      */
    156     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
    157     private AmbientState mAmbientState = new AmbientState();
    158     private NotificationGroupManager mGroupManager;
    159     private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
    160     private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
    161     private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
    162     private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
    163     private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
    164     private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
    165     private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
    166     private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
    167     private ArrayList<View> mSwipedOutViews = new ArrayList<>();
    168     private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
    169     private boolean mAnimationsEnabled;
    170     private boolean mChangePositionInProgress;
    171     private boolean mChildTransferInProgress;
    172 
    173     /**
    174      * The raw amount of the overScroll on the top, which is not rubber-banded.
    175      */
    176     private float mOverScrolledTopPixels;
    177 
    178     /**
    179      * The raw amount of the overScroll on the bottom, which is not rubber-banded.
    180      */
    181     private float mOverScrolledBottomPixels;
    182     private OnChildLocationsChangedListener mListener;
    183     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
    184     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
    185     private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
    186     private boolean mNeedsAnimation;
    187     private boolean mTopPaddingNeedsAnimation;
    188     private boolean mDimmedNeedsAnimation;
    189     private boolean mHideSensitiveNeedsAnimation;
    190     private boolean mDarkNeedsAnimation;
    191     private int mDarkAnimationOriginIndex;
    192     private boolean mActivateNeedsAnimation;
    193     private boolean mGoToFullShadeNeedsAnimation;
    194     private boolean mIsExpanded = true;
    195     private boolean mChildrenUpdateRequested;
    196     private boolean mIsExpansionChanging;
    197     private boolean mPanelTracking;
    198     private boolean mExpandingNotification;
    199     private boolean mExpandedInThisMotion;
    200     private boolean mScrollingEnabled;
    201     private DismissView mDismissView;
    202     private EmptyShadeView mEmptyShadeView;
    203     private boolean mDismissAllInProgress;
    204 
    205     /**
    206      * Was the scroller scrolled to the top when the down motion was observed?
    207      */
    208     private boolean mScrolledToTopOnFirstDown;
    209     /**
    210      * The minimal amount of over scroll which is needed in order to switch to the quick settings
    211      * when over scrolling on a expanded card.
    212      */
    213     private float mMinTopOverScrollToEscape;
    214     private int mIntrinsicPadding;
    215     private float mStackTranslation;
    216     private float mTopPaddingOverflow;
    217     private boolean mDontReportNextOverScroll;
    218     private boolean mDontClampNextScroll;
    219     private boolean mRequestViewResizeAnimationOnLayout;
    220     private boolean mNeedViewResizeAnimation;
    221     private View mExpandedGroupView;
    222     private boolean mEverythingNeedsAnimation;
    223 
    224     /**
    225      * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
    226      * This is needed to avoid scrolling too far after the notification was collapsed in the same
    227      * motion.
    228      */
    229     private int mMaxScrollAfterExpand;
    230     private SwipeHelper.LongPressListener mLongPressListener;
    231 
    232     private NotificationSettingsIconRow mCurrIconRow;
    233     private View mTranslatingParentView;
    234     private View mGearExposedView;
    235 
    236     /**
    237      * Should in this touch motion only be scrolling allowed? It's true when the scroller was
    238      * animating.
    239      */
    240     private boolean mOnlyScrollingInThisMotion;
    241     private boolean mDisallowDismissInThisMotion;
    242     private boolean mInterceptDelegateEnabled;
    243     private boolean mDelegateToScrollView;
    244     private boolean mDisallowScrollingInThisMotion;
    245     private long mGoToFullShadeDelay;
    246     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
    247             = new ViewTreeObserver.OnPreDrawListener() {
    248         @Override
    249         public boolean onPreDraw() {
    250             updateForcedScroll();
    251             updateChildren();
    252             mChildrenUpdateRequested = false;
    253             getViewTreeObserver().removeOnPreDrawListener(this);
    254             return true;
    255         }
    256     };
    257     private PhoneStatusBar mPhoneStatusBar;
    258     private int[] mTempInt2 = new int[2];
    259     private boolean mGenerateChildOrderChangedEvent;
    260     private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
    261     private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
    262     private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
    263             = new HashSet<>();
    264     private HeadsUpManager mHeadsUpManager;
    265     private boolean mTrackingHeadsUp;
    266     private ScrimController mScrimController;
    267     private boolean mForceNoOverlappingRendering;
    268     private NotificationOverflowContainer mOverflowContainer;
    269     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
    270     private FalsingManager mFalsingManager;
    271     private boolean mAnimationRunning;
    272     private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
    273             = new ViewTreeObserver.OnPreDrawListener() {
    274         @Override
    275         public boolean onPreDraw() {
    276             // if it needs animation
    277             if (!mNeedsAnimation && !mChildrenUpdateRequested) {
    278                 updateBackground();
    279             }
    280             return true;
    281         }
    282     };
    283     private Rect mBackgroundBounds = new Rect();
    284     private Rect mStartAnimationRect = new Rect();
    285     private Rect mEndAnimationRect = new Rect();
    286     private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
    287     private boolean mAnimateNextBackgroundBottom;
    288     private boolean mAnimateNextBackgroundTop;
    289     private ObjectAnimator mBottomAnimator = null;
    290     private ObjectAnimator mTopAnimator = null;
    291     private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
    292     private ActivatableNotificationView mLastVisibleBackgroundChild = null;
    293     private int mBgColor;
    294     private float mDimAmount;
    295     private ValueAnimator mDimAnimator;
    296     private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
    297     private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
    298         @Override
    299         public void onAnimationEnd(Animator animation) {
    300             mDimAnimator = null;
    301         }
    302     };
    303     private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
    304             = new ValueAnimator.AnimatorUpdateListener() {
    305 
    306         @Override
    307         public void onAnimationUpdate(ValueAnimator animation) {
    308             setDimAmount((Float) animation.getAnimatedValue());
    309         }
    310     };
    311     protected ViewGroup mQsContainer;
    312     private boolean mContinuousShadowUpdate;
    313     private ViewTreeObserver.OnPreDrawListener mShadowUpdater
    314             = new ViewTreeObserver.OnPreDrawListener() {
    315 
    316         @Override
    317         public boolean onPreDraw() {
    318             updateViewShadows();
    319             return true;
    320         }
    321     };
    322     private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
    323         @Override
    324         public int compare(ExpandableView view, ExpandableView otherView) {
    325             float endY = view.getTranslationY() + view.getActualHeight();
    326             float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
    327             if (endY < otherEndY) {
    328                 return -1;
    329             } else if (endY > otherEndY) {
    330                 return 1;
    331             } else {
    332                 // The two notifications end at the same location
    333                 return 0;
    334             }
    335         }
    336     };
    337     private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
    338     private boolean mPulsing;
    339     private boolean mDrawBackgroundAsSrc;
    340     private boolean mFadingOut;
    341     private boolean mParentFadingOut;
    342     private boolean mGroupExpandedForMeasure;
    343     private boolean mScrollable;
    344     private View mForcedScroll;
    345     private float mBackgroundFadeAmount = 1.0f;
    346     private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE =
    347             new FloatProperty<NotificationStackScrollLayout>("backgroundFade") {
    348                 @Override
    349                 public void setValue(NotificationStackScrollLayout object, float value) {
    350                     object.setBackgroundFadeAmount(value);
    351                 }
    352 
    353                 @Override
    354                 public Float get(NotificationStackScrollLayout object) {
    355                     return object.getBackgroundFadeAmount();
    356                 }
    357             };
    358 
    359     public NotificationStackScrollLayout(Context context) {
    360         this(context, null);
    361     }
    362 
    363     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
    364         this(context, attrs, 0);
    365     }
    366 
    367     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    368         this(context, attrs, defStyleAttr, 0);
    369     }
    370 
    371     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
    372             int defStyleRes) {
    373         super(context, attrs, defStyleAttr, defStyleRes);
    374         mBgColor = context.getColor(R.color.notification_shade_background_color);
    375         int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
    376         int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
    377         mExpandHelper = new ExpandHelper(getContext(), this,
    378                 minHeight, maxHeight);
    379         mExpandHelper.setEventSource(this);
    380         mExpandHelper.setScrollAdapter(this);
    381         mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
    382         mSwipeHelper.setLongPressListener(mLongPressListener);
    383         mStackScrollAlgorithm = new StackScrollAlgorithm(context);
    384         initView(context);
    385         setWillNotDraw(false);
    386         if (DEBUG) {
    387             mDebugPaint = new Paint();
    388             mDebugPaint.setColor(0xffff0000);
    389             mDebugPaint.setStrokeWidth(2);
    390             mDebugPaint.setStyle(Paint.Style.STROKE);
    391         }
    392         mFalsingManager = FalsingManager.getInstance(context);
    393     }
    394 
    395     @Override
    396     public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
    397         if (mLongPressListener != null) {
    398             MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
    399                     row.getStatusBarNotification().getPackageName());
    400             mLongPressListener.onLongPress(row, x, y);
    401         }
    402     }
    403 
    404     @Override
    405     public void onSettingsIconRowReset(ExpandableNotificationRow row) {
    406         if (mTranslatingParentView != null && row == mTranslatingParentView) {
    407             mSwipeHelper.setSnappedToGear(false);
    408             mGearExposedView = null;
    409             mTranslatingParentView = null;
    410         }
    411     }
    412 
    413     @Override
    414     protected void onDraw(Canvas canvas) {
    415         if (mCurrentBounds.top < mCurrentBounds.bottom) {
    416             canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
    417                     mBackgroundPaint);
    418         }
    419         if (DEBUG) {
    420             int y = mTopPadding;
    421             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
    422             y = (int) (getLayoutHeight() - mBottomStackPeekSize
    423                     - mBottomStackSlowDownHeight);
    424             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
    425             y = (int) (getLayoutHeight() - mBottomStackPeekSize);
    426             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
    427             y = (int) getLayoutHeight();
    428             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
    429             y = getHeight() - getEmptyBottomMargin();
    430             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
    431         }
    432     }
    433 
    434     private void updateBackgroundDimming() {
    435         float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
    436         alpha *= mBackgroundFadeAmount;
    437         // We need to manually blend in the background color
    438         int scrimColor = mScrimController.getScrimBehindColor();
    439         // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
    440         float alphaInv = 1 - alpha;
    441         int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
    442                 (int) (mBackgroundFadeAmount * Color.red(mBgColor)
    443                         + alphaInv * Color.red(scrimColor)),
    444                 (int) (mBackgroundFadeAmount * Color.green(mBgColor)
    445                         + alphaInv * Color.green(scrimColor)),
    446                 (int) (mBackgroundFadeAmount * Color.blue(mBgColor)
    447                         + alphaInv * Color.blue(scrimColor)));
    448         mBackgroundPaint.setColor(color);
    449         invalidate();
    450     }
    451 
    452     private void initView(Context context) {
    453         mScroller = new OverScroller(getContext());
    454         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
    455         setClipChildren(false);
    456         final ViewConfiguration configuration = ViewConfiguration.get(context);
    457         mTouchSlop = configuration.getScaledTouchSlop();
    458         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
    459         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    460         mOverflingDistance = configuration.getScaledOverflingDistance();
    461         mCollapsedSize = context.getResources()
    462                 .getDimensionPixelSize(R.dimen.notification_min_height);
    463         mBottomStackPeekSize = context.getResources()
    464                 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
    465         mStackScrollAlgorithm.initView(context);
    466         mPaddingBetweenElements = Math.max(1, context.getResources()
    467                 .getDimensionPixelSize(R.dimen.notification_divider_height));
    468         mIncreasedPaddingBetweenElements = context.getResources()
    469                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
    470         mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
    471         mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
    472                 R.dimen.min_top_overscroll_to_qs);
    473     }
    474 
    475     public void setDrawBackgroundAsSrc(boolean asSrc) {
    476         mDrawBackgroundAsSrc = asSrc;
    477         updateSrcDrawing();
    478     }
    479 
    480     private void updateSrcDrawing() {
    481         mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && (!mFadingOut && !mParentFadingOut)
    482                 ? mSrcMode : null);
    483         invalidate();
    484     }
    485 
    486     private void notifyHeightChangeListener(ExpandableView view) {
    487         if (mOnHeightChangedListener != null) {
    488             mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
    489         }
    490     }
    491 
    492     @Override
    493     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    494         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    495         // We need to measure all children even the GONE ones, such that the heights are calculated
    496         // correctly as they are used to calculate how many we can fit on the screen.
    497         final int size = getChildCount();
    498         for (int i = 0; i < size; i++) {
    499             measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
    500         }
    501     }
    502 
    503     @Override
    504     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    505         // we layout all our children centered on the top
    506         float centerX = getWidth() / 2.0f;
    507         for (int i = 0; i < getChildCount(); i++) {
    508             View child = getChildAt(i);
    509             // We need to layout all children even the GONE ones, such that the heights are
    510             // calculated correctly as they are used to calculate how many we can fit on the screen
    511             float width = child.getMeasuredWidth();
    512             float height = child.getMeasuredHeight();
    513             child.layout((int) (centerX - width / 2.0f),
    514                     0,
    515                     (int) (centerX + width / 2.0f),
    516                     (int) height);
    517         }
    518         setMaxLayoutHeight(getHeight());
    519         updateContentHeight();
    520         clampScrollPosition();
    521         if (mRequestViewResizeAnimationOnLayout) {
    522             requestAnimationOnViewResize(null);
    523             mRequestViewResizeAnimationOnLayout = false;
    524         }
    525         requestChildrenUpdate();
    526         updateFirstAndLastBackgroundViews();
    527     }
    528 
    529     private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
    530         if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
    531             mNeedViewResizeAnimation = true;
    532             mNeedsAnimation = true;
    533         }
    534     }
    535 
    536     public void updateSpeedBumpIndex(int newIndex) {
    537         mAmbientState.setSpeedBumpIndex(newIndex);
    538     }
    539 
    540     public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
    541         mListener = listener;
    542     }
    543 
    544     /**
    545      * Returns the location the given child is currently rendered at.
    546      *
    547      * @param child the child to get the location for
    548      * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
    549      */
    550     public int getChildLocation(View child) {
    551         StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
    552         if (childViewState == null) {
    553             return StackViewState.LOCATION_UNKNOWN;
    554         }
    555         if (childViewState.gone) {
    556             return StackViewState.LOCATION_GONE;
    557         }
    558         return childViewState.location;
    559     }
    560 
    561     private void setMaxLayoutHeight(int maxLayoutHeight) {
    562         mMaxLayoutHeight = maxLayoutHeight;
    563         updateAlgorithmHeightAndPadding();
    564     }
    565 
    566     private void updateAlgorithmHeightAndPadding() {
    567         mAmbientState.setLayoutHeight(getLayoutHeight());
    568         mAmbientState.setTopPadding(mTopPadding);
    569     }
    570 
    571     /**
    572      * Updates the children views according to the stack scroll algorithm. Call this whenever
    573      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
    574      */
    575     private void updateChildren() {
    576         updateScrollStateForAddedChildren();
    577         mAmbientState.setScrollY(mOwnScrollY);
    578         mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
    579         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
    580             applyCurrentState();
    581         } else {
    582             startAnimationToState();
    583         }
    584     }
    585 
    586     private void updateScrollStateForAddedChildren() {
    587         if (mChildrenToAddAnimated.isEmpty()) {
    588             return;
    589         }
    590         for (int i = 0; i < getChildCount(); i++) {
    591             ExpandableView child = (ExpandableView) getChildAt(i);
    592             if (mChildrenToAddAnimated.contains(child)) {
    593                 int startingPosition = getPositionInLinearLayout(child);
    594                 int padding = child.getIncreasedPaddingAmount() == 1.0f
    595                         ? mIncreasedPaddingBetweenElements :
    596                         mPaddingBetweenElements;
    597                 int childHeight = getIntrinsicHeight(child) + padding;
    598                 if (startingPosition < mOwnScrollY) {
    599                     // This child starts off screen, so let's keep it offscreen to keep the others visible
    600 
    601                     mOwnScrollY += childHeight;
    602                 }
    603             }
    604         }
    605         clampScrollPosition();
    606     }
    607 
    608     private void updateForcedScroll() {
    609         if (mForcedScroll != null && (!mForcedScroll.hasFocus()
    610                 || !mForcedScroll.isAttachedToWindow())) {
    611             mForcedScroll = null;
    612         }
    613         if (mForcedScroll != null) {
    614             ExpandableView expandableView = (ExpandableView) mForcedScroll;
    615             int positionInLinearLayout = getPositionInLinearLayout(expandableView);
    616             int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
    617             int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
    618 
    619             targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
    620 
    621             // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
    622             // that it is not visible anymore.
    623             if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
    624                 mOwnScrollY = targetScroll;
    625             }
    626         }
    627     }
    628 
    629     private void requestChildrenUpdate() {
    630         if (!mChildrenUpdateRequested) {
    631             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
    632             mChildrenUpdateRequested = true;
    633             invalidate();
    634         }
    635     }
    636 
    637     private boolean isCurrentlyAnimating() {
    638         return mStateAnimator.isRunning();
    639     }
    640 
    641     private void clampScrollPosition() {
    642         int scrollRange = getScrollRange();
    643         if (scrollRange < mOwnScrollY) {
    644             mOwnScrollY = scrollRange;
    645         }
    646     }
    647 
    648     public int getTopPadding() {
    649         return mTopPadding;
    650     }
    651 
    652     private void setTopPadding(int topPadding, boolean animate) {
    653         if (mTopPadding != topPadding) {
    654             mTopPadding = topPadding;
    655             updateAlgorithmHeightAndPadding();
    656             updateContentHeight();
    657             if (animate && mAnimationsEnabled && mIsExpanded) {
    658                 mTopPaddingNeedsAnimation = true;
    659                 mNeedsAnimation =  true;
    660             }
    661             requestChildrenUpdate();
    662             notifyHeightChangeListener(null);
    663         }
    664     }
    665 
    666     /**
    667      * Update the height of the stack to a new height.
    668      *
    669      * @param height the new height of the stack
    670      */
    671     public void setStackHeight(float height) {
    672         mLastSetStackHeight = height;
    673         setIsExpanded(height > 0.0f);
    674         int stackHeight;
    675         float translationY;
    676         float appearEndPosition = getAppearEndPosition();
    677         float appearStartPosition = getAppearStartPosition();
    678         if (height >= appearEndPosition) {
    679             translationY = mTopPaddingOverflow;
    680             stackHeight = (int) height;
    681         } else {
    682             float appearFraction = getAppearFraction(height);
    683             if (appearFraction >= 0) {
    684                 translationY = NotificationUtils.interpolate(getExpandTranslationStart(), 0,
    685                         appearFraction);
    686             } else {
    687                 // This may happen when pushing up a heads up. We linearly push it up from the
    688                 // start
    689                 translationY = height - appearStartPosition + getExpandTranslationStart();
    690             }
    691             stackHeight = (int) (height - translationY);
    692         }
    693         if (stackHeight != mCurrentStackHeight) {
    694             mCurrentStackHeight = stackHeight;
    695             updateAlgorithmHeightAndPadding();
    696             requestChildrenUpdate();
    697         }
    698         setStackTranslation(translationY);
    699     }
    700 
    701     /**
    702      * @return The translation at the beginning when expanding.
    703      *         Measured relative to the resting position.
    704      */
    705     private float getExpandTranslationStart() {
    706         int startPosition = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()
    707                 ? 0 : -getFirstChildIntrinsicHeight();
    708         return startPosition - mTopPadding;
    709     }
    710 
    711     /**
    712      * @return the position from where the appear transition starts when expanding.
    713      *         Measured in absolute height.
    714      */
    715     private float getAppearStartPosition() {
    716         return mTrackingHeadsUp
    717                 ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
    718                 : 0;
    719     }
    720 
    721     /**
    722      * @return the position from where the appear transition ends when expanding.
    723      *         Measured in absolute height.
    724      */
    725     private float getAppearEndPosition() {
    726         int firstItemHeight = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()
    727                 ? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize
    728                         + mBottomStackSlowDownHeight
    729                 : getLayoutMinHeight();
    730         return firstItemHeight + mTopPadding + mTopPaddingOverflow;
    731     }
    732 
    733     /**
    734      * @param height the height of the panel
    735      * @return the fraction of the appear animation that has been performed
    736      */
    737     public float getAppearFraction(float height) {
    738         float appearEndPosition = getAppearEndPosition();
    739         float appearStartPosition = getAppearStartPosition();
    740         return (height - appearStartPosition)
    741                 / (appearEndPosition - appearStartPosition);
    742     }
    743 
    744     public float getStackTranslation() {
    745         return mStackTranslation;
    746     }
    747 
    748     private void setStackTranslation(float stackTranslation) {
    749         if (stackTranslation != mStackTranslation) {
    750             mStackTranslation = stackTranslation;
    751             mAmbientState.setStackTranslation(stackTranslation);
    752             requestChildrenUpdate();
    753         }
    754     }
    755 
    756     /**
    757      * Get the current height of the view. This is at most the msize of the view given by a the
    758      * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
    759      *
    760      * @return either the layout height or the externally defined height, whichever is smaller
    761      */
    762     private int getLayoutHeight() {
    763         return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
    764     }
    765 
    766     public int getFirstItemMinHeight() {
    767         final ExpandableView firstChild = getFirstChildNotGone();
    768         return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
    769     }
    770 
    771     public int getBottomStackPeekSize() {
    772         return mBottomStackPeekSize;
    773     }
    774 
    775     public int getBottomStackSlowDownHeight() {
    776         return mBottomStackSlowDownHeight;
    777     }
    778 
    779     public void setLongPressListener(SwipeHelper.LongPressListener listener) {
    780         mSwipeHelper.setLongPressListener(listener);
    781         mLongPressListener = listener;
    782     }
    783 
    784     public void setQsContainer(ViewGroup qsContainer) {
    785         mQsContainer = qsContainer;
    786     }
    787 
    788     @Override
    789     public void onChildDismissed(View v) {
    790         ExpandableNotificationRow row = (ExpandableNotificationRow) v;
    791         if (!row.isDismissed()) {
    792             handleChildDismissed(v);
    793         }
    794         ViewGroup transientContainer = row.getTransientContainer();
    795         if (transientContainer != null) {
    796             transientContainer.removeTransientView(v);
    797         }
    798     }
    799 
    800     private void handleChildDismissed(View v) {
    801         if (mDismissAllInProgress) {
    802             return;
    803         }
    804         setSwipingInProgress(false);
    805         if (mDragAnimPendingChildren.contains(v)) {
    806             // We start the swipe and finish it in the same frame, we don't want any animation
    807             // for the drag
    808             mDragAnimPendingChildren.remove(v);
    809         }
    810         mSwipedOutViews.add(v);
    811         mAmbientState.onDragFinished(v);
    812         updateContinuousShadowDrawing();
    813         if (v instanceof ExpandableNotificationRow) {
    814             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
    815             if (row.isHeadsUp()) {
    816                 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
    817             }
    818         }
    819         performDismiss(v, mGroupManager, false /* fromAccessibility */);
    820 
    821         mFalsingManager.onNotificationDismissed();
    822         if (mFalsingManager.shouldEnforceBouncer()) {
    823             mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
    824                     false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
    825         }
    826     }
    827 
    828     public static void performDismiss(View v, NotificationGroupManager groupManager,
    829             boolean fromAccessibility) {
    830         if (!(v instanceof ExpandableNotificationRow)) {
    831             return;
    832         }
    833         ExpandableNotificationRow row = (ExpandableNotificationRow) v;
    834         if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) {
    835             ExpandableNotificationRow groupSummary =
    836                     groupManager.getLogicalGroupSummary(row.getStatusBarNotification());
    837             if (groupSummary.isClearable()) {
    838                 performDismiss(groupSummary, groupManager, fromAccessibility);
    839             }
    840         }
    841         row.setDismissed(true, fromAccessibility);
    842         if (row.isClearable()) {
    843             row.performDismiss();
    844         }
    845         if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
    846     }
    847 
    848     @Override
    849     public void onChildSnappedBack(View animView, float targetLeft) {
    850         mAmbientState.onDragFinished(animView);
    851         updateContinuousShadowDrawing();
    852         if (!mDragAnimPendingChildren.contains(animView)) {
    853             if (mAnimationsEnabled) {
    854                 mSnappedBackChildren.add(animView);
    855                 mNeedsAnimation = true;
    856             }
    857             requestChildrenUpdate();
    858         } else {
    859             // We start the swipe and snap back in the same frame, we don't want any animation
    860             mDragAnimPendingChildren.remove(animView);
    861         }
    862         if (mCurrIconRow != null && targetLeft == 0) {
    863             mCurrIconRow.resetState();
    864             mCurrIconRow = null;
    865         }
    866     }
    867 
    868     @Override
    869     public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
    870         if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
    871             mScrimController.setTopHeadsUpDragAmount(animView,
    872                     Math.min(Math.abs(swipeProgress / 2f - 1.0f), 1.0f));
    873         }
    874         return true; // Don't fade out the notification
    875     }
    876 
    877     @Override
    878     public void onBeginDrag(View v) {
    879         mFalsingManager.onNotificatonStartDismissing();
    880         setSwipingInProgress(true);
    881         mAmbientState.onBeginDrag(v);
    882         updateContinuousShadowDrawing();
    883         if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
    884             mDragAnimPendingChildren.add(v);
    885             mNeedsAnimation = true;
    886         }
    887         requestChildrenUpdate();
    888     }
    889 
    890     public static boolean isPinnedHeadsUp(View v) {
    891         if (v instanceof ExpandableNotificationRow) {
    892             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
    893             return row.isHeadsUp() && row.isPinned();
    894         }
    895         return false;
    896     }
    897 
    898     private boolean isHeadsUp(View v) {
    899         if (v instanceof ExpandableNotificationRow) {
    900             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
    901             return row.isHeadsUp();
    902         }
    903         return false;
    904     }
    905 
    906     @Override
    907     public void onDragCancelled(View v) {
    908         mFalsingManager.onNotificatonStopDismissing();
    909         setSwipingInProgress(false);
    910     }
    911 
    912     @Override
    913     public float getFalsingThresholdFactor() {
    914         return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
    915     }
    916 
    917     @Override
    918     public View getChildAtPosition(MotionEvent ev) {
    919         View child = getChildAtPosition(ev.getX(), ev.getY());
    920         if (child instanceof ExpandableNotificationRow) {
    921             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
    922             ExpandableNotificationRow parent = row.getNotificationParent();
    923             if (parent != null && parent.areChildrenExpanded()
    924                     && (parent.areGutsExposed()
    925                         || mGearExposedView == parent
    926                         || (parent.getNotificationChildren().size() == 1
    927                                 && parent.isClearable()))) {
    928                 // In this case the group is expanded and showing the gear for the
    929                 // group, further interaction should apply to the group, not any
    930                 // child notifications so we use the parent of the child. We also do the same
    931                 // if we only have a single child.
    932                 child = parent;
    933             }
    934         }
    935         return child;
    936     }
    937 
    938     public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
    939         getLocationOnScreen(mTempInt2);
    940         float localTouchY = touchY - mTempInt2[1];
    941 
    942         ExpandableView closestChild = null;
    943         float minDist = Float.MAX_VALUE;
    944 
    945         // find the view closest to the location, accounting for GONE views
    946         final int count = getChildCount();
    947         for (int childIdx = 0; childIdx < count; childIdx++) {
    948             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
    949             if (slidingChild.getVisibility() == GONE
    950                     || slidingChild instanceof StackScrollerDecorView) {
    951                 continue;
    952             }
    953             float childTop = slidingChild.getTranslationY();
    954             float top = childTop + slidingChild.getClipTopAmount();
    955             float bottom = childTop + slidingChild.getActualHeight();
    956 
    957             float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
    958             if (dist < minDist) {
    959                 closestChild = slidingChild;
    960                 minDist = dist;
    961             }
    962         }
    963         return closestChild;
    964     }
    965 
    966     @Override
    967     public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
    968         getLocationOnScreen(mTempInt2);
    969         return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
    970     }
    971 
    972     @Override
    973     public ExpandableView getChildAtPosition(float touchX, float touchY) {
    974         // find the view under the pointer, accounting for GONE views
    975         final int count = getChildCount();
    976         for (int childIdx = 0; childIdx < count; childIdx++) {
    977             ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
    978             if (slidingChild.getVisibility() == GONE
    979                     || slidingChild instanceof StackScrollerDecorView) {
    980                 continue;
    981             }
    982             float childTop = slidingChild.getTranslationY();
    983             float top = childTop + slidingChild.getClipTopAmount();
    984             float bottom = childTop + slidingChild.getActualHeight();
    985 
    986             // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
    987             // camera affordance).
    988             int left = 0;
    989             int right = getWidth();
    990 
    991             if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
    992                 if (slidingChild instanceof ExpandableNotificationRow) {
    993                     ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
    994                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
    995                             && mHeadsUpManager.getTopEntry().entry.row != row
    996                             && mGroupManager.getGroupSummary(
    997                                 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
    998                                 != row) {
    999                         continue;
   1000                     }
   1001                     return row.getViewAtPosition(touchY - childTop);
   1002                 }
   1003                 return slidingChild;
   1004             }
   1005         }
   1006         return null;
   1007     }
   1008 
   1009     @Override
   1010     public boolean canChildBeExpanded(View v) {
   1011         return v instanceof ExpandableNotificationRow
   1012                 && ((ExpandableNotificationRow) v).isExpandable()
   1013                 && !((ExpandableNotificationRow) v).areGutsExposed()
   1014                 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
   1015     }
   1016 
   1017     /* Only ever called as a consequence of an expansion gesture in the shade. */
   1018     @Override
   1019     public void setUserExpandedChild(View v, boolean userExpanded) {
   1020         if (v instanceof ExpandableNotificationRow) {
   1021             ExpandableNotificationRow row = (ExpandableNotificationRow) v;
   1022             row.setUserExpanded(userExpanded, true /* allowChildrenExpansion */);
   1023             row.onExpandedByGesture(userExpanded);
   1024         }
   1025     }
   1026 
   1027     @Override
   1028     public void setExpansionCancelled(View v) {
   1029         if (v instanceof ExpandableNotificationRow) {
   1030             ((ExpandableNotificationRow) v).setGroupExpansionChanging(false);
   1031         }
   1032     }
   1033 
   1034     @Override
   1035     public void setUserLockedChild(View v, boolean userLocked) {
   1036         if (v instanceof ExpandableNotificationRow) {
   1037             ((ExpandableNotificationRow) v).setUserLocked(userLocked);
   1038         }
   1039         removeLongPressCallback();
   1040         requestDisallowInterceptTouchEvent(true);
   1041     }
   1042 
   1043     @Override
   1044     public void expansionStateChanged(boolean isExpanding) {
   1045         mExpandingNotification = isExpanding;
   1046         if (!mExpandedInThisMotion) {
   1047             mMaxScrollAfterExpand = mOwnScrollY;
   1048             mExpandedInThisMotion = true;
   1049         }
   1050     }
   1051 
   1052     @Override
   1053     public int getMaxExpandHeight(ExpandableView view) {
   1054         int maxContentHeight = view.getMaxContentHeight();
   1055         if (view.isSummaryWithChildren()) {
   1056             // Faking a measure with the group expanded to simulate how the group would look if
   1057             // it was. Doing a calculation here would be highly non-trivial because of the
   1058             // algorithm
   1059             mGroupExpandedForMeasure = true;
   1060             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
   1061             mGroupManager.toggleGroupExpansion(row.getStatusBarNotification());
   1062             row.setForceUnlocked(true);
   1063             mAmbientState.setLayoutHeight(mMaxLayoutHeight);
   1064             mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
   1065             mAmbientState.setLayoutHeight(getLayoutHeight());
   1066             mGroupManager.toggleGroupExpansion(
   1067                     row.getStatusBarNotification());
   1068             mGroupExpandedForMeasure = false;
   1069             row.setForceUnlocked(false);
   1070             int height = mCurrentStackScrollState.getViewStateForView(view).height;
   1071             return Math.min(height, maxContentHeight);
   1072         }
   1073         return maxContentHeight;
   1074     }
   1075 
   1076     public void setScrollingEnabled(boolean enable) {
   1077         mScrollingEnabled = enable;
   1078     }
   1079 
   1080     @Override
   1081     public void lockScrollTo(View v) {
   1082         if (mForcedScroll == v) {
   1083             return;
   1084         }
   1085         mForcedScroll = v;
   1086         scrollTo(v);
   1087     }
   1088 
   1089     @Override
   1090     public boolean scrollTo(View v) {
   1091         ExpandableView expandableView = (ExpandableView) v;
   1092         int positionInLinearLayout = getPositionInLinearLayout(v);
   1093         int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
   1094         int outOfViewScroll = positionInLinearLayout + expandableView.getIntrinsicHeight();
   1095 
   1096         // Only apply the scroll if we're scrolling the view upwards, or the view is so far up
   1097         // that it is not visible anymore.
   1098         if (mOwnScrollY < targetScroll || outOfViewScroll < mOwnScrollY) {
   1099             mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
   1100             mDontReportNextOverScroll = true;
   1101             postInvalidateOnAnimation();
   1102             return true;
   1103         }
   1104         return false;
   1105     }
   1106 
   1107     /**
   1108      * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
   1109      *         the IME.
   1110      */
   1111     private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
   1112         return positionInLinearLayout + v.getIntrinsicHeight() +
   1113                 getImeInset() - getHeight() + getTopPadding();
   1114     }
   1115 
   1116     @Override
   1117     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
   1118         mBottomInset = insets.getSystemWindowInsetBottom();
   1119 
   1120         int range = getScrollRange();
   1121         if (mOwnScrollY > range) {
   1122             // HACK: We're repeatedly getting staggered insets here while the IME is
   1123             // animating away. To work around that we'll wait until things have settled.
   1124             removeCallbacks(mReclamp);
   1125             postDelayed(mReclamp, 50);
   1126         } else if (mForcedScroll != null) {
   1127             // The scroll was requested before we got the actual inset - in case we need
   1128             // to scroll up some more do so now.
   1129             scrollTo(mForcedScroll);
   1130         }
   1131         return insets;
   1132     }
   1133 
   1134     private Runnable mReclamp = new Runnable() {
   1135         @Override
   1136         public void run() {
   1137             int range = getScrollRange();
   1138             mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
   1139             mDontReportNextOverScroll = true;
   1140             mDontClampNextScroll = true;
   1141             postInvalidateOnAnimation();
   1142         }
   1143     };
   1144 
   1145     public void setExpandingEnabled(boolean enable) {
   1146         mExpandHelper.setEnabled(enable);
   1147     }
   1148 
   1149     private boolean isScrollingEnabled() {
   1150         return mScrollingEnabled;
   1151     }
   1152 
   1153     @Override
   1154     public boolean canChildBeDismissed(View v) {
   1155         return StackScrollAlgorithm.canChildBeDismissed(v);
   1156     }
   1157 
   1158     @Override
   1159     public boolean isAntiFalsingNeeded() {
   1160         return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
   1161     }
   1162 
   1163     private void setSwipingInProgress(boolean isSwiped) {
   1164         mSwipingInProgress = isSwiped;
   1165         if(isSwiped) {
   1166             requestDisallowInterceptTouchEvent(true);
   1167         }
   1168     }
   1169 
   1170     @Override
   1171     protected void onConfigurationChanged(Configuration newConfig) {
   1172         super.onConfigurationChanged(newConfig);
   1173         float densityScale = getResources().getDisplayMetrics().density;
   1174         mSwipeHelper.setDensityScale(densityScale);
   1175         float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
   1176         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
   1177         initView(getContext());
   1178     }
   1179 
   1180     public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
   1181         mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
   1182                 true /* isDismissAll */);
   1183     }
   1184 
   1185     public void snapViewIfNeeded(ExpandableNotificationRow child) {
   1186         boolean animate = mIsExpanded || isPinnedHeadsUp(child);
   1187         // If the child is showing the gear to go to settings, snap to that
   1188         float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0;
   1189         mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
   1190     }
   1191 
   1192     @Override
   1193     public boolean onTouchEvent(MotionEvent ev) {
   1194         boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
   1195                 || ev.getActionMasked()== MotionEvent.ACTION_UP;
   1196         handleEmptySpaceClick(ev);
   1197         boolean expandWantsIt = false;
   1198         if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
   1199             if (isCancelOrUp) {
   1200                 mExpandHelper.onlyObserveMovements(false);
   1201             }
   1202             boolean wasExpandingBefore = mExpandingNotification;
   1203             expandWantsIt = mExpandHelper.onTouchEvent(ev);
   1204             if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
   1205                     && !mDisallowScrollingInThisMotion) {
   1206                 dispatchDownEventToScroller(ev);
   1207             }
   1208         }
   1209         boolean scrollerWantsIt = false;
   1210         if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
   1211                 && !mDisallowScrollingInThisMotion) {
   1212             scrollerWantsIt = onScrollTouch(ev);
   1213         }
   1214         boolean horizontalSwipeWantsIt = false;
   1215         if (!mIsBeingDragged
   1216                 && !mExpandingNotification
   1217                 && !mExpandedInThisMotion
   1218                 && !mOnlyScrollingInThisMotion
   1219                 && !mDisallowDismissInThisMotion) {
   1220             horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
   1221         }
   1222         return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
   1223     }
   1224 
   1225     private void dispatchDownEventToScroller(MotionEvent ev) {
   1226         MotionEvent downEvent = MotionEvent.obtain(ev);
   1227         downEvent.setAction(MotionEvent.ACTION_DOWN);
   1228         onScrollTouch(downEvent);
   1229         downEvent.recycle();
   1230     }
   1231 
   1232     private boolean onScrollTouch(MotionEvent ev) {
   1233         if (!isScrollingEnabled()) {
   1234             return false;
   1235         }
   1236         if (ev.getY() < mQsContainer.getBottom()) {
   1237             return false;
   1238         }
   1239         mForcedScroll = null;
   1240         initVelocityTrackerIfNotExists();
   1241         mVelocityTracker.addMovement(ev);
   1242 
   1243         final int action = ev.getAction();
   1244 
   1245         switch (action & MotionEvent.ACTION_MASK) {
   1246             case MotionEvent.ACTION_DOWN: {
   1247                 if (getChildCount() == 0 || !isInContentBounds(ev)) {
   1248                     return false;
   1249                 }
   1250                 boolean isBeingDragged = !mScroller.isFinished();
   1251                 setIsBeingDragged(isBeingDragged);
   1252 
   1253                 /*
   1254                  * If being flinged and user touches, stop the fling. isFinished
   1255                  * will be false if being flinged.
   1256                  */
   1257                 if (!mScroller.isFinished()) {
   1258                     mScroller.forceFinished(true);
   1259                 }
   1260 
   1261                 // Remember where the motion event started
   1262                 mLastMotionY = (int) ev.getY();
   1263                 mDownX = (int) ev.getX();
   1264                 mActivePointerId = ev.getPointerId(0);
   1265                 break;
   1266             }
   1267             case MotionEvent.ACTION_MOVE:
   1268                 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
   1269                 if (activePointerIndex == -1) {
   1270                     Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
   1271                     break;
   1272                 }
   1273 
   1274                 final int y = (int) ev.getY(activePointerIndex);
   1275                 final int x = (int) ev.getX(activePointerIndex);
   1276                 int deltaY = mLastMotionY - y;
   1277                 final int xDiff = Math.abs(x - mDownX);
   1278                 final int yDiff = Math.abs(deltaY);
   1279                 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
   1280                     setIsBeingDragged(true);
   1281                     if (deltaY > 0) {
   1282                         deltaY -= mTouchSlop;
   1283                     } else {
   1284                         deltaY += mTouchSlop;
   1285                     }
   1286                 }
   1287                 if (mIsBeingDragged) {
   1288                     // Scroll to follow the motion event
   1289                     mLastMotionY = y;
   1290                     int range = getScrollRange();
   1291                     if (mExpandedInThisMotion) {
   1292                         range = Math.min(range, mMaxScrollAfterExpand);
   1293                     }
   1294 
   1295                     float scrollAmount;
   1296                     if (deltaY < 0) {
   1297                         scrollAmount = overScrollDown(deltaY);
   1298                     } else {
   1299                         scrollAmount = overScrollUp(deltaY, range);
   1300                     }
   1301 
   1302                     // Calling overScrollBy will call onOverScrolled, which
   1303                     // calls onScrollChanged if applicable.
   1304                     if (scrollAmount != 0.0f) {
   1305                         // The scrolling motion could not be compensated with the
   1306                         // existing overScroll, we have to scroll the view
   1307                         overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
   1308                                 0, range, 0, getHeight() / 2, true);
   1309                     }
   1310                 }
   1311                 break;
   1312             case MotionEvent.ACTION_UP:
   1313                 if (mIsBeingDragged) {
   1314                     final VelocityTracker velocityTracker = mVelocityTracker;
   1315                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
   1316                     int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
   1317 
   1318                     if (shouldOverScrollFling(initialVelocity)) {
   1319                         onOverScrollFling(true, initialVelocity);
   1320                     } else {
   1321                         if (getChildCount() > 0) {
   1322                             if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
   1323                                 float currentOverScrollTop = getCurrentOverScrollAmount(true);
   1324                                 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
   1325                                     fling(-initialVelocity);
   1326                                 } else {
   1327                                     onOverScrollFling(false, initialVelocity);
   1328                                 }
   1329                             } else {
   1330                                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
   1331                                         getScrollRange())) {
   1332                                     postInvalidateOnAnimation();
   1333                                 }
   1334                             }
   1335                         }
   1336                     }
   1337 
   1338                     mActivePointerId = INVALID_POINTER;
   1339                     endDrag();
   1340                 }
   1341 
   1342                 break;
   1343             case MotionEvent.ACTION_CANCEL:
   1344                 if (mIsBeingDragged && getChildCount() > 0) {
   1345                     if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
   1346                         postInvalidateOnAnimation();
   1347                     }
   1348                     mActivePointerId = INVALID_POINTER;
   1349                     endDrag();
   1350                 }
   1351                 break;
   1352             case MotionEvent.ACTION_POINTER_DOWN: {
   1353                 final int index = ev.getActionIndex();
   1354                 mLastMotionY = (int) ev.getY(index);
   1355                 mDownX = (int) ev.getX(index);
   1356                 mActivePointerId = ev.getPointerId(index);
   1357                 break;
   1358             }
   1359             case MotionEvent.ACTION_POINTER_UP:
   1360                 onSecondaryPointerUp(ev);
   1361                 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
   1362                 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
   1363                 break;
   1364         }
   1365         return true;
   1366     }
   1367 
   1368     private void onOverScrollFling(boolean open, int initialVelocity) {
   1369         if (mOverscrollTopChangedListener != null) {
   1370             mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
   1371         }
   1372         mDontReportNextOverScroll = true;
   1373         setOverScrollAmount(0.0f, true, false);
   1374     }
   1375 
   1376     /**
   1377      * Perform a scroll upwards and adapt the overscroll amounts accordingly
   1378      *
   1379      * @param deltaY The amount to scroll upwards, has to be positive.
   1380      * @return The amount of scrolling to be performed by the scroller,
   1381      *         not handled by the overScroll amount.
   1382      */
   1383     private float overScrollUp(int deltaY, int range) {
   1384         deltaY = Math.max(deltaY, 0);
   1385         float currentTopAmount = getCurrentOverScrollAmount(true);
   1386         float newTopAmount = currentTopAmount - deltaY;
   1387         if (currentTopAmount > 0) {
   1388             setOverScrollAmount(newTopAmount, true /* onTop */,
   1389                     false /* animate */);
   1390         }
   1391         // Top overScroll might not grab all scrolling motion,
   1392         // we have to scroll as well.
   1393         float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
   1394         float newScrollY = mOwnScrollY + scrollAmount;
   1395         if (newScrollY > range) {
   1396             if (!mExpandedInThisMotion) {
   1397                 float currentBottomPixels = getCurrentOverScrolledPixels(false);
   1398                 // We overScroll on the top
   1399                 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
   1400                         false /* onTop */,
   1401                         false /* animate */);
   1402             }
   1403             mOwnScrollY = range;
   1404             scrollAmount = 0.0f;
   1405         }
   1406         return scrollAmount;
   1407     }
   1408 
   1409     /**
   1410      * Perform a scroll downward and adapt the overscroll amounts accordingly
   1411      *
   1412      * @param deltaY The amount to scroll downwards, has to be negative.
   1413      * @return The amount of scrolling to be performed by the scroller,
   1414      *         not handled by the overScroll amount.
   1415      */
   1416     private float overScrollDown(int deltaY) {
   1417         deltaY = Math.min(deltaY, 0);
   1418         float currentBottomAmount = getCurrentOverScrollAmount(false);
   1419         float newBottomAmount = currentBottomAmount + deltaY;
   1420         if (currentBottomAmount > 0) {
   1421             setOverScrollAmount(newBottomAmount, false /* onTop */,
   1422                     false /* animate */);
   1423         }
   1424         // Bottom overScroll might not grab all scrolling motion,
   1425         // we have to scroll as well.
   1426         float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
   1427         float newScrollY = mOwnScrollY + scrollAmount;
   1428         if (newScrollY < 0) {
   1429             float currentTopPixels = getCurrentOverScrolledPixels(true);
   1430             // We overScroll on the top
   1431             setOverScrolledPixels(currentTopPixels - newScrollY,
   1432                     true /* onTop */,
   1433                     false /* animate */);
   1434             mOwnScrollY = 0;
   1435             scrollAmount = 0.0f;
   1436         }
   1437         return scrollAmount;
   1438     }
   1439 
   1440     private void onSecondaryPointerUp(MotionEvent ev) {
   1441         final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
   1442                 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
   1443         final int pointerId = ev.getPointerId(pointerIndex);
   1444         if (pointerId == mActivePointerId) {
   1445             // This was our active pointer going up. Choose a new
   1446             // active pointer and adjust accordingly.
   1447             // TODO: Make this decision more intelligent.
   1448             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
   1449             mLastMotionY = (int) ev.getY(newPointerIndex);
   1450             mActivePointerId = ev.getPointerId(newPointerIndex);
   1451             if (mVelocityTracker != null) {
   1452                 mVelocityTracker.clear();
   1453             }
   1454         }
   1455     }
   1456 
   1457     private void initVelocityTrackerIfNotExists() {
   1458         if (mVelocityTracker == null) {
   1459             mVelocityTracker = VelocityTracker.obtain();
   1460         }
   1461     }
   1462 
   1463     private void recycleVelocityTracker() {
   1464         if (mVelocityTracker != null) {
   1465             mVelocityTracker.recycle();
   1466             mVelocityTracker = null;
   1467         }
   1468     }
   1469 
   1470     private void initOrResetVelocityTracker() {
   1471         if (mVelocityTracker == null) {
   1472             mVelocityTracker = VelocityTracker.obtain();
   1473         } else {
   1474             mVelocityTracker.clear();
   1475         }
   1476     }
   1477 
   1478     public void setFinishScrollingCallback(Runnable runnable) {
   1479         mFinishScrollingCallback = runnable;
   1480     }
   1481 
   1482     @Override
   1483     public void computeScroll() {
   1484         if (mScroller.computeScrollOffset()) {
   1485             // This is called at drawing time by ViewGroup.
   1486             int oldX = mScrollX;
   1487             int oldY = mOwnScrollY;
   1488             int x = mScroller.getCurrX();
   1489             int y = mScroller.getCurrY();
   1490 
   1491             if (oldX != x || oldY != y) {
   1492                 int range = getScrollRange();
   1493                 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
   1494                     float currVelocity = mScroller.getCurrVelocity();
   1495                     if (currVelocity >= mMinimumVelocity) {
   1496                         mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
   1497                     }
   1498                 }
   1499 
   1500                 if (mDontClampNextScroll) {
   1501                     range = Math.max(range, oldY);
   1502                 }
   1503                 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
   1504                         0, (int) (mMaxOverScroll), false);
   1505                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
   1506             }
   1507 
   1508             // Keep on drawing until the animation has finished.
   1509             postInvalidateOnAnimation();
   1510         } else {
   1511             mDontClampNextScroll = false;
   1512             if (mFinishScrollingCallback != null) {
   1513                 mFinishScrollingCallback.run();
   1514             }
   1515         }
   1516     }
   1517 
   1518     @Override
   1519     protected boolean overScrollBy(int deltaX, int deltaY,
   1520             int scrollX, int scrollY,
   1521             int scrollRangeX, int scrollRangeY,
   1522             int maxOverScrollX, int maxOverScrollY,
   1523             boolean isTouchEvent) {
   1524 
   1525         int newScrollY = scrollY + deltaY;
   1526 
   1527         final int top = -maxOverScrollY;
   1528         final int bottom = maxOverScrollY + scrollRangeY;
   1529 
   1530         boolean clampedY = false;
   1531         if (newScrollY > bottom) {
   1532             newScrollY = bottom;
   1533             clampedY = true;
   1534         } else if (newScrollY < top) {
   1535             newScrollY = top;
   1536             clampedY = true;
   1537         }
   1538 
   1539         onOverScrolled(0, newScrollY, false, clampedY);
   1540 
   1541         return clampedY;
   1542     }
   1543 
   1544     /**
   1545      * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
   1546      * overscroll effect based on numPixels. By default this will also cancel animations on the
   1547      * same overScroll edge.
   1548      *
   1549      * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
   1550      *                  the rubber-banding logic.
   1551      * @param onTop Should the effect be applied on top of the scroller.
   1552      * @param animate Should an animation be performed.
   1553      */
   1554     public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
   1555         setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
   1556     }
   1557 
   1558     /**
   1559      * Set the effective overScroll amount which will be directly reflected in the layout.
   1560      * By default this will also cancel animations on the same overScroll edge.
   1561      *
   1562      * @param amount The amount to overScroll by.
   1563      * @param onTop Should the effect be applied on top of the scroller.
   1564      * @param animate Should an animation be performed.
   1565      */
   1566     public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
   1567         setOverScrollAmount(amount, onTop, animate, true);
   1568     }
   1569 
   1570     /**
   1571      * Set the effective overScroll amount which will be directly reflected in the layout.
   1572      *
   1573      * @param amount The amount to overScroll by.
   1574      * @param onTop Should the effect be applied on top of the scroller.
   1575      * @param animate Should an animation be performed.
   1576      * @param cancelAnimators Should running animations be cancelled.
   1577      */
   1578     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
   1579             boolean cancelAnimators) {
   1580         setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
   1581     }
   1582 
   1583     /**
   1584      * Set the effective overScroll amount which will be directly reflected in the layout.
   1585      *
   1586      * @param amount The amount to overScroll by.
   1587      * @param onTop Should the effect be applied on top of the scroller.
   1588      * @param animate Should an animation be performed.
   1589      * @param cancelAnimators Should running animations be cancelled.
   1590      * @param isRubberbanded The value which will be passed to
   1591      *                     {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
   1592      */
   1593     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
   1594             boolean cancelAnimators, boolean isRubberbanded) {
   1595         if (cancelAnimators) {
   1596             mStateAnimator.cancelOverScrollAnimators(onTop);
   1597         }
   1598         setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
   1599     }
   1600 
   1601     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
   1602             boolean isRubberbanded) {
   1603         amount = Math.max(0, amount);
   1604         if (animate) {
   1605             mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
   1606         } else {
   1607             setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
   1608             mAmbientState.setOverScrollAmount(amount, onTop);
   1609             if (onTop) {
   1610                 notifyOverscrollTopListener(amount, isRubberbanded);
   1611             }
   1612             requestChildrenUpdate();
   1613         }
   1614     }
   1615 
   1616     private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
   1617         mExpandHelper.onlyObserveMovements(amount > 1.0f);
   1618         if (mDontReportNextOverScroll) {
   1619             mDontReportNextOverScroll = false;
   1620             return;
   1621         }
   1622         if (mOverscrollTopChangedListener != null) {
   1623             mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
   1624         }
   1625     }
   1626 
   1627     public void setOverscrollTopChangedListener(
   1628             OnOverscrollTopChangedListener overscrollTopChangedListener) {
   1629         mOverscrollTopChangedListener = overscrollTopChangedListener;
   1630     }
   1631 
   1632     public float getCurrentOverScrollAmount(boolean top) {
   1633         return mAmbientState.getOverScrollAmount(top);
   1634     }
   1635 
   1636     public float getCurrentOverScrolledPixels(boolean top) {
   1637         return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
   1638     }
   1639 
   1640     private void setOverScrolledPixels(float amount, boolean onTop) {
   1641         if (onTop) {
   1642             mOverScrolledTopPixels = amount;
   1643         } else {
   1644             mOverScrolledBottomPixels = amount;
   1645         }
   1646     }
   1647 
   1648     private void customScrollTo(int y) {
   1649         mOwnScrollY = y;
   1650         updateChildren();
   1651     }
   1652 
   1653     @Override
   1654     protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
   1655         // Treat animating scrolls differently; see #computeScroll() for why.
   1656         if (!mScroller.isFinished()) {
   1657             final int oldX = mScrollX;
   1658             final int oldY = mOwnScrollY;
   1659             mScrollX = scrollX;
   1660             mOwnScrollY = scrollY;
   1661             if (clampedY) {
   1662                 springBack();
   1663             } else {
   1664                 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
   1665                 invalidateParentIfNeeded();
   1666                 updateChildren();
   1667                 float overScrollTop = getCurrentOverScrollAmount(true);
   1668                 if (mOwnScrollY < 0) {
   1669                     notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
   1670                 } else {
   1671                     notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
   1672                 }
   1673             }
   1674         } else {
   1675             customScrollTo(scrollY);
   1676             scrollTo(scrollX, mScrollY);
   1677         }
   1678     }
   1679 
   1680     private void springBack() {
   1681         int scrollRange = getScrollRange();
   1682         boolean overScrolledTop = mOwnScrollY <= 0;
   1683         boolean overScrolledBottom = mOwnScrollY >= scrollRange;
   1684         if (overScrolledTop || overScrolledBottom) {
   1685             boolean onTop;
   1686             float newAmount;
   1687             if (overScrolledTop) {
   1688                 onTop = true;
   1689                 newAmount = -mOwnScrollY;
   1690                 mOwnScrollY = 0;
   1691                 mDontReportNextOverScroll = true;
   1692             } else {
   1693                 onTop = false;
   1694                 newAmount = mOwnScrollY - scrollRange;
   1695                 mOwnScrollY = scrollRange;
   1696             }
   1697             setOverScrollAmount(newAmount, onTop, false);
   1698             setOverScrollAmount(0.0f, onTop, true);
   1699             mScroller.forceFinished(true);
   1700         }
   1701     }
   1702 
   1703     private int getScrollRange() {
   1704         int contentHeight = getContentHeight();
   1705         int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
   1706                 + mBottomStackSlowDownHeight);
   1707         int imeInset = getImeInset();
   1708         scrollRange += Math.min(imeInset, Math.max(0,
   1709                 getContentHeight() - (getHeight() - imeInset)));
   1710         return scrollRange;
   1711     }
   1712 
   1713     private int getImeInset() {
   1714         return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
   1715     }
   1716 
   1717     /**
   1718      * @return the first child which has visibility unequal to GONE
   1719      */
   1720     public ExpandableView getFirstChildNotGone() {
   1721         int childCount = getChildCount();
   1722         for (int i = 0; i < childCount; i++) {
   1723             View child = getChildAt(i);
   1724             if (child.getVisibility() != View.GONE) {
   1725                 return (ExpandableView) child;
   1726             }
   1727         }
   1728         return null;
   1729     }
   1730 
   1731     /**
   1732      * @return the child before the given view which has visibility unequal to GONE
   1733      */
   1734     public ExpandableView getViewBeforeView(ExpandableView view) {
   1735         ExpandableView previousView = null;
   1736         int childCount = getChildCount();
   1737         for (int i = 0; i < childCount; i++) {
   1738             View child = getChildAt(i);
   1739             if (child == view) {
   1740                 return previousView;
   1741             }
   1742             if (child.getVisibility() != View.GONE) {
   1743                 previousView = (ExpandableView) child;
   1744             }
   1745         }
   1746         return null;
   1747     }
   1748 
   1749     /**
   1750      * @return The first child which has visibility unequal to GONE which is currently below the
   1751      *         given translationY or equal to it.
   1752      */
   1753     private View getFirstChildBelowTranlsationY(float translationY) {
   1754         int childCount = getChildCount();
   1755         for (int i = 0; i < childCount; i++) {
   1756             View child = getChildAt(i);
   1757             if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
   1758                 return child;
   1759             }
   1760         }
   1761         return null;
   1762     }
   1763 
   1764     /**
   1765      * @return the last child which has visibility unequal to GONE
   1766      */
   1767     public View getLastChildNotGone() {
   1768         int childCount = getChildCount();
   1769         for (int i = childCount - 1; i >= 0; i--) {
   1770             View child = getChildAt(i);
   1771             if (child.getVisibility() != View.GONE) {
   1772                 return child;
   1773             }
   1774         }
   1775         return null;
   1776     }
   1777 
   1778     /**
   1779      * @return the number of children which have visibility unequal to GONE
   1780      */
   1781     public int getNotGoneChildCount() {
   1782         int childCount = getChildCount();
   1783         int count = 0;
   1784         for (int i = 0; i < childCount; i++) {
   1785             ExpandableView child = (ExpandableView) getChildAt(i);
   1786             if (child.getVisibility() != View.GONE && !child.willBeGone()) {
   1787                 count++;
   1788             }
   1789         }
   1790         return count;
   1791     }
   1792 
   1793     public int getContentHeight() {
   1794         return mContentHeight;
   1795     }
   1796 
   1797     private void updateContentHeight() {
   1798         int height = 0;
   1799         float previousIncreasedAmount = 0.0f;
   1800         for (int i = 0; i < getChildCount(); i++) {
   1801             ExpandableView expandableView = (ExpandableView) getChildAt(i);
   1802             if (expandableView.getVisibility() != View.GONE) {
   1803                 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
   1804                 if (height != 0) {
   1805                     height += (int) NotificationUtils.interpolate(
   1806                             mPaddingBetweenElements,
   1807                             mIncreasedPaddingBetweenElements,
   1808                             Math.max(previousIncreasedAmount, increasedPaddingAmount));
   1809                 }
   1810                 previousIncreasedAmount = increasedPaddingAmount;
   1811                 height += expandableView.getIntrinsicHeight();
   1812             }
   1813         }
   1814         mContentHeight = height + mTopPadding;
   1815         updateScrollability();
   1816     }
   1817 
   1818     private void updateScrollability() {
   1819         boolean scrollable = getScrollRange() > 0;
   1820         if (scrollable != mScrollable) {
   1821             mScrollable = scrollable;
   1822             setFocusable(scrollable);
   1823         }
   1824     }
   1825 
   1826     private void updateBackground() {
   1827         if (mAmbientState.isDark()) {
   1828             return;
   1829         }
   1830         updateBackgroundBounds();
   1831         if (!mCurrentBounds.equals(mBackgroundBounds)) {
   1832             if (mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom || areBoundsAnimating()) {
   1833                 startBackgroundAnimation();
   1834             } else {
   1835                 mCurrentBounds.set(mBackgroundBounds);
   1836                 applyCurrentBackgroundBounds();
   1837             }
   1838         } else {
   1839             if (mBottomAnimator != null) {
   1840                 mBottomAnimator.cancel();
   1841             }
   1842             if (mTopAnimator != null) {
   1843                 mTopAnimator.cancel();
   1844             }
   1845         }
   1846         mAnimateNextBackgroundBottom = false;
   1847         mAnimateNextBackgroundTop = false;
   1848     }
   1849 
   1850     private boolean areBoundsAnimating() {
   1851         return mBottomAnimator != null || mTopAnimator != null;
   1852     }
   1853 
   1854     private void startBackgroundAnimation() {
   1855         // left and right are always instantly applied
   1856         mCurrentBounds.left = mBackgroundBounds.left;
   1857         mCurrentBounds.right = mBackgroundBounds.right;
   1858         startBottomAnimation();
   1859         startTopAnimation();
   1860     }
   1861 
   1862     private void startTopAnimation() {
   1863         int previousEndValue = mEndAnimationRect.top;
   1864         int newEndValue = mBackgroundBounds.top;
   1865         ObjectAnimator previousAnimator = mTopAnimator;
   1866         if (previousAnimator != null && previousEndValue == newEndValue) {
   1867             return;
   1868         }
   1869         if (!mAnimateNextBackgroundTop) {
   1870             // just a local update was performed
   1871             if (previousAnimator != null) {
   1872                 // we need to increase all animation keyframes of the previous animator by the
   1873                 // relative change to the end value
   1874                 int previousStartValue = mStartAnimationRect.top;
   1875                 PropertyValuesHolder[] values = previousAnimator.getValues();
   1876                 values[0].setIntValues(previousStartValue, newEndValue);
   1877                 mStartAnimationRect.top = previousStartValue;
   1878                 mEndAnimationRect.top = newEndValue;
   1879                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
   1880                 return;
   1881             } else {
   1882                 // no new animation needed, let's just apply the value
   1883                 setBackgroundTop(newEndValue);
   1884                 return;
   1885             }
   1886         }
   1887         if (previousAnimator != null) {
   1888             previousAnimator.cancel();
   1889         }
   1890         ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
   1891                 mCurrentBounds.top, newEndValue);
   1892         Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
   1893         animator.setInterpolator(interpolator);
   1894         animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
   1895         // remove the tag when the animation is finished
   1896         animator.addListener(new AnimatorListenerAdapter() {
   1897             @Override
   1898             public void onAnimationEnd(Animator animation) {
   1899                 mStartAnimationRect.top = -1;
   1900                 mEndAnimationRect.top = -1;
   1901                 mTopAnimator = null;
   1902             }
   1903         });
   1904         animator.start();
   1905         mStartAnimationRect.top = mCurrentBounds.top;
   1906         mEndAnimationRect.top = newEndValue;
   1907         mTopAnimator = animator;
   1908     }
   1909 
   1910     private void startBottomAnimation() {
   1911         int previousStartValue = mStartAnimationRect.bottom;
   1912         int previousEndValue = mEndAnimationRect.bottom;
   1913         int newEndValue = mBackgroundBounds.bottom;
   1914         ObjectAnimator previousAnimator = mBottomAnimator;
   1915         if (previousAnimator != null && previousEndValue == newEndValue) {
   1916             return;
   1917         }
   1918         if (!mAnimateNextBackgroundBottom) {
   1919             // just a local update was performed
   1920             if (previousAnimator != null) {
   1921                 // we need to increase all animation keyframes of the previous animator by the
   1922                 // relative change to the end value
   1923                 PropertyValuesHolder[] values = previousAnimator.getValues();
   1924                 values[0].setIntValues(previousStartValue, newEndValue);
   1925                 mStartAnimationRect.bottom = previousStartValue;
   1926                 mEndAnimationRect.bottom = newEndValue;
   1927                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
   1928                 return;
   1929             } else {
   1930                 // no new animation needed, let's just apply the value
   1931                 setBackgroundBottom(newEndValue);
   1932                 return;
   1933             }
   1934         }
   1935         if (previousAnimator != null) {
   1936             previousAnimator.cancel();
   1937         }
   1938         ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
   1939                 mCurrentBounds.bottom, newEndValue);
   1940         Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
   1941         animator.setInterpolator(interpolator);
   1942         animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
   1943         // remove the tag when the animation is finished
   1944         animator.addListener(new AnimatorListenerAdapter() {
   1945             @Override
   1946             public void onAnimationEnd(Animator animation) {
   1947                 mStartAnimationRect.bottom = -1;
   1948                 mEndAnimationRect.bottom = -1;
   1949                 mBottomAnimator = null;
   1950             }
   1951         });
   1952         animator.start();
   1953         mStartAnimationRect.bottom = mCurrentBounds.bottom;
   1954         mEndAnimationRect.bottom = newEndValue;
   1955         mBottomAnimator = animator;
   1956     }
   1957 
   1958     private void setBackgroundTop(int top) {
   1959         mCurrentBounds.top = top;
   1960         applyCurrentBackgroundBounds();
   1961     }
   1962 
   1963     public void setBackgroundBottom(int bottom) {
   1964         mCurrentBounds.bottom = bottom;
   1965         applyCurrentBackgroundBounds();
   1966     }
   1967 
   1968     private void applyCurrentBackgroundBounds() {
   1969         if (!mFadingOut) {
   1970             mScrimController.setExcludedBackgroundArea(mCurrentBounds);
   1971         }
   1972         invalidate();
   1973     }
   1974 
   1975     /**
   1976      * Update the background bounds to the new desired bounds
   1977      */
   1978     private void updateBackgroundBounds() {
   1979         mBackgroundBounds.left = (int) getX();
   1980         mBackgroundBounds.right = (int) (getX() + getWidth());
   1981         if (!mIsExpanded) {
   1982             mBackgroundBounds.top = 0;
   1983             mBackgroundBounds.bottom = 0;
   1984         }
   1985         ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
   1986         int top = 0;
   1987         if (firstView != null) {
   1988             int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView);
   1989             if (mAnimateNextBackgroundTop
   1990                     || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
   1991                     || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
   1992                 // we're ending up at the same location as we are now, lets just skip the animation
   1993                 top = finalTranslationY;
   1994             } else {
   1995                 top = (int) firstView.getTranslationY();
   1996             }
   1997         }
   1998         ActivatableNotificationView lastView = mLastVisibleBackgroundChild;
   1999         int bottom = 0;
   2000         if (lastView != null) {
   2001             int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView);
   2002             int finalHeight = StackStateAnimator.getFinalActualHeight(lastView);
   2003             int finalBottom = finalTranslationY + finalHeight;
   2004             finalBottom = Math.min(finalBottom, getHeight());
   2005             if (mAnimateNextBackgroundBottom
   2006                     || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
   2007                     || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
   2008                 // we're ending up at the same location as we are now, lets just skip the animation
   2009                 bottom = finalBottom;
   2010             } else {
   2011                 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
   2012                 bottom = Math.min(bottom, getHeight());
   2013             }
   2014         } else {
   2015             top = mTopPadding;
   2016             bottom = top;
   2017         }
   2018         if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
   2019             top = (int) Math.max(mTopPadding + mStackTranslation, top);
   2020         } else {
   2021             // otherwise the animation from the shade to the keyguard will jump as it's maxed
   2022             top = Math.max(0, top);
   2023         }
   2024         mBackgroundBounds.top = top;
   2025         mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
   2026     }
   2027 
   2028     private ActivatableNotificationView getFirstPinnedHeadsUp() {
   2029         int childCount = getChildCount();
   2030         for (int i = 0; i < childCount; i++) {
   2031             View child = getChildAt(i);
   2032             if (child.getVisibility() != View.GONE
   2033                     && child instanceof ExpandableNotificationRow) {
   2034                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
   2035                 if (row.isPinned()) {
   2036                     return row;
   2037                 }
   2038             }
   2039         }
   2040         return null;
   2041     }
   2042 
   2043     private ActivatableNotificationView getLastChildWithBackground() {
   2044         int childCount = getChildCount();
   2045         for (int i = childCount - 1; i >= 0; i--) {
   2046             View child = getChildAt(i);
   2047             if (child.getVisibility() != View.GONE
   2048                     && child instanceof ActivatableNotificationView) {
   2049                 return (ActivatableNotificationView) child;
   2050             }
   2051         }
   2052         return null;
   2053     }
   2054 
   2055     private ActivatableNotificationView getFirstChildWithBackground() {
   2056         int childCount = getChildCount();
   2057         for (int i = 0; i < childCount; i++) {
   2058             View child = getChildAt(i);
   2059             if (child.getVisibility() != View.GONE
   2060                     && child instanceof ActivatableNotificationView) {
   2061                 return (ActivatableNotificationView) child;
   2062             }
   2063         }
   2064         return null;
   2065     }
   2066 
   2067     /**
   2068      * Fling the scroll view
   2069      *
   2070      * @param velocityY The initial velocity in the Y direction. Positive
   2071      *                  numbers mean that the finger/cursor is moving down the screen,
   2072      *                  which means we want to scroll towards the top.
   2073      */
   2074     private void fling(int velocityY) {
   2075         if (getChildCount() > 0) {
   2076             int scrollRange = getScrollRange();
   2077 
   2078             float topAmount = getCurrentOverScrollAmount(true);
   2079             float bottomAmount = getCurrentOverScrollAmount(false);
   2080             if (velocityY < 0 && topAmount > 0) {
   2081                 mOwnScrollY -= (int) topAmount;
   2082                 mDontReportNextOverScroll = true;
   2083                 setOverScrollAmount(0, true, false);
   2084                 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
   2085                         * mOverflingDistance + topAmount;
   2086             } else if (velocityY > 0 && bottomAmount > 0) {
   2087                 mOwnScrollY += bottomAmount;
   2088                 setOverScrollAmount(0, false, false);
   2089                 mMaxOverScroll = Math.abs(velocityY) / 1000f
   2090                         * getRubberBandFactor(false /* onTop */) * mOverflingDistance
   2091                         +  bottomAmount;
   2092             } else {
   2093                 // it will be set once we reach the boundary
   2094                 mMaxOverScroll = 0.0f;
   2095             }
   2096             int minScrollY = Math.max(0, scrollRange);
   2097             if (mExpandedInThisMotion) {
   2098                 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
   2099             }
   2100             mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
   2101                     minScrollY, 0, mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
   2102 
   2103             postInvalidateOnAnimation();
   2104         }
   2105     }
   2106 
   2107     /**
   2108      * @return Whether a fling performed on the top overscroll edge lead to the expanded
   2109      * overScroll view (i.e QS).
   2110      */
   2111     private boolean shouldOverScrollFling(int initialVelocity) {
   2112         float topOverScroll = getCurrentOverScrollAmount(true);
   2113         return mScrolledToTopOnFirstDown
   2114                 && !mExpandedInThisMotion
   2115                 && topOverScroll > mMinTopOverScrollToEscape
   2116                 && initialVelocity > 0;
   2117     }
   2118 
   2119     /**
   2120      * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
   2121      * account.
   2122      *
   2123      * @param qsHeight the top padding imposed by the quick settings panel
   2124      * @param animate whether to animate the change
   2125      * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
   2126      *                               {@code qsHeight} is the final top padding
   2127      */
   2128     public void updateTopPadding(float qsHeight, boolean animate,
   2129             boolean ignoreIntrinsicPadding) {
   2130         float start = qsHeight;
   2131         float stackHeight = getHeight() - start;
   2132         int minStackHeight = getLayoutMinHeight();
   2133         if (stackHeight <= minStackHeight) {
   2134             float overflow = minStackHeight - stackHeight;
   2135             stackHeight = minStackHeight;
   2136             start = getHeight() - stackHeight;
   2137             mTopPaddingOverflow = overflow;
   2138         } else {
   2139             mTopPaddingOverflow = 0;
   2140         }
   2141         setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
   2142                 animate);
   2143         setStackHeight(mLastSetStackHeight);
   2144     }
   2145 
   2146     public int getLayoutMinHeight() {
   2147         int firstChildMinHeight = getFirstChildIntrinsicHeight();
   2148         return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
   2149                 mMaxLayoutHeight - mTopPadding);
   2150     }
   2151 
   2152     public int getFirstChildIntrinsicHeight() {
   2153         final ExpandableView firstChild = getFirstChildNotGone();
   2154         int firstChildMinHeight = firstChild != null
   2155                 ? firstChild.getIntrinsicHeight()
   2156                 : mEmptyShadeView != null
   2157                         ? mEmptyShadeView.getIntrinsicHeight()
   2158                         : mCollapsedSize;
   2159         if (mOwnScrollY > 0) {
   2160             firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
   2161         }
   2162         return firstChildMinHeight;
   2163     }
   2164 
   2165     public float getTopPaddingOverflow() {
   2166         return mTopPaddingOverflow;
   2167     }
   2168 
   2169     public int getPeekHeight() {
   2170         final ExpandableView firstChild = getFirstChildNotGone();
   2171         final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
   2172                 : mCollapsedSize;
   2173         return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
   2174                 + mBottomStackSlowDownHeight;
   2175     }
   2176 
   2177     private int clampPadding(int desiredPadding) {
   2178         return Math.max(desiredPadding, mIntrinsicPadding);
   2179     }
   2180 
   2181     private float getRubberBandFactor(boolean onTop) {
   2182         if (!onTop) {
   2183             return RUBBER_BAND_FACTOR_NORMAL;
   2184         }
   2185         if (mExpandedInThisMotion) {
   2186             return RUBBER_BAND_FACTOR_AFTER_EXPAND;
   2187         } else if (mIsExpansionChanging || mPanelTracking) {
   2188             return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
   2189         } else if (mScrolledToTopOnFirstDown) {
   2190             return 1.0f;
   2191         }
   2192         return RUBBER_BAND_FACTOR_NORMAL;
   2193     }
   2194 
   2195     /**
   2196      * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
   2197      * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
   2198      * overscroll view (e.g. expand QS).
   2199      */
   2200     private boolean isRubberbanded(boolean onTop) {
   2201         return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
   2202                 || !mScrolledToTopOnFirstDown;
   2203     }
   2204 
   2205     private void endDrag() {
   2206         setIsBeingDragged(false);
   2207 
   2208         recycleVelocityTracker();
   2209 
   2210         if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
   2211             setOverScrollAmount(0, true /* onTop */, true /* animate */);
   2212         }
   2213         if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
   2214             setOverScrollAmount(0, false /* onTop */, true /* animate */);
   2215         }
   2216     }
   2217 
   2218     private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
   2219         ev.offsetLocation(sourceView.getX(), sourceView.getY());
   2220         ev.offsetLocation(-targetView.getX(), -targetView.getY());
   2221     }
   2222 
   2223     @Override
   2224     public boolean onInterceptTouchEvent(MotionEvent ev) {
   2225         initDownStates(ev);
   2226         handleEmptySpaceClick(ev);
   2227         boolean expandWantsIt = false;
   2228         if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
   2229             expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
   2230         }
   2231         boolean scrollWantsIt = false;
   2232         if (!mSwipingInProgress && !mExpandingNotification) {
   2233             scrollWantsIt = onInterceptTouchEventScroll(ev);
   2234         }
   2235         boolean swipeWantsIt = false;
   2236         if (!mIsBeingDragged
   2237                 && !mExpandingNotification
   2238                 && !mExpandedInThisMotion
   2239                 && !mOnlyScrollingInThisMotion
   2240                 && !mDisallowDismissInThisMotion) {
   2241             swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
   2242         }
   2243         return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
   2244     }
   2245 
   2246     private void handleEmptySpaceClick(MotionEvent ev) {
   2247         switch (ev.getActionMasked()) {
   2248             case MotionEvent.ACTION_MOVE:
   2249                 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
   2250                         || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
   2251                     mTouchIsClick = false;
   2252                 }
   2253                 break;
   2254             case MotionEvent.ACTION_UP:
   2255                 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
   2256                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
   2257                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
   2258                 }
   2259                 break;
   2260         }
   2261     }
   2262 
   2263     private void initDownStates(MotionEvent ev) {
   2264         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
   2265             mExpandedInThisMotion = false;
   2266             mOnlyScrollingInThisMotion = !mScroller.isFinished();
   2267             mDisallowScrollingInThisMotion = false;
   2268             mDisallowDismissInThisMotion = false;
   2269             mTouchIsClick = true;
   2270             mInitialTouchX = ev.getX();
   2271             mInitialTouchY = ev.getY();
   2272         }
   2273     }
   2274 
   2275     public void setChildTransferInProgress(boolean childTransferInProgress) {
   2276         mChildTransferInProgress = childTransferInProgress;
   2277     }
   2278 
   2279     @Override
   2280     public void onViewRemoved(View child) {
   2281         super.onViewRemoved(child);
   2282         // we only call our internal methods if this is actually a removal and not just a
   2283         // notification which becomes a child notification
   2284         if (!mChildTransferInProgress) {
   2285             onViewRemovedInternal(child, this);
   2286         }
   2287     }
   2288 
   2289     @Override
   2290     public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
   2291         super.requestDisallowInterceptTouchEvent(disallowIntercept);
   2292         if (disallowIntercept) {
   2293             mSwipeHelper.removeLongPressCallback();
   2294         }
   2295     }
   2296 
   2297     private void onViewRemovedInternal(View child, ViewGroup container) {
   2298         if (mChangePositionInProgress) {
   2299             // This is only a position change, don't do anything special
   2300             return;
   2301         }
   2302         ExpandableView expandableView = (ExpandableView) child;
   2303         expandableView.setOnHeightChangedListener(null);
   2304         mCurrentStackScrollState.removeViewStateForView(child);
   2305         updateScrollStateForRemovedChild(expandableView);
   2306         boolean animationGenerated = generateRemoveAnimation(child);
   2307         if (animationGenerated) {
   2308             if (!mSwipedOutViews.contains(child)) {
   2309                 container.getOverlay().add(child);
   2310             } else if (Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) {
   2311                 container.addTransientView(child, 0);
   2312                 expandableView.setTransientContainer(container);
   2313             }
   2314         } else {
   2315             mSwipedOutViews.remove(child);
   2316         }
   2317         updateAnimationState(false, child);
   2318 
   2319         // Make sure the clipRect we might have set is removed
   2320         expandableView.setClipTopAmount(0);
   2321 
   2322         focusNextViewIfFocused(child);
   2323     }
   2324 
   2325     private void focusNextViewIfFocused(View view) {
   2326         if (view instanceof ExpandableNotificationRow) {
   2327             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
   2328             if (row.shouldRefocusOnDismiss()) {
   2329                 View nextView = row.getChildAfterViewWhenDismissed();
   2330                 if (nextView == null) {
   2331                     View groupParentWhenDismissed = row.getGroupParentWhenDismissed();
   2332                     nextView = getFirstChildBelowTranlsationY(groupParentWhenDismissed != null
   2333                             ? groupParentWhenDismissed.getTranslationY()
   2334                             : view.getTranslationY());
   2335                 }
   2336                 if (nextView != null) {
   2337                     nextView.requestAccessibilityFocus();
   2338                 }
   2339             }
   2340         }
   2341 
   2342     }
   2343 
   2344     private boolean isChildInGroup(View child) {
   2345         return child instanceof ExpandableNotificationRow
   2346                 && mGroupManager.isChildInGroupWithSummary(
   2347                         ((ExpandableNotificationRow) child).getStatusBarNotification());
   2348     }
   2349 
   2350     /**
   2351      * Generate a remove animation for a child view.
   2352      *
   2353      * @param child The view to generate the remove animation for.
   2354      * @return Whether an animation was generated.
   2355      */
   2356     private boolean generateRemoveAnimation(View child) {
   2357         if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
   2358             mAddedHeadsUpChildren.remove(child);
   2359             return false;
   2360         }
   2361         if (isClickedHeadsUp(child)) {
   2362             // An animation is already running, add it to the Overlay
   2363             mClearOverlayViewsWhenFinished.add(child);
   2364             return true;
   2365         }
   2366         if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
   2367             if (!mChildrenToAddAnimated.contains(child)) {
   2368                 // Generate Animations
   2369                 mChildrenToRemoveAnimated.add(child);
   2370                 mNeedsAnimation = true;
   2371                 return true;
   2372             } else {
   2373                 mChildrenToAddAnimated.remove(child);
   2374                 mFromMoreCardAdditions.remove(child);
   2375                 return false;
   2376             }
   2377         }
   2378         return false;
   2379     }
   2380 
   2381     private boolean isClickedHeadsUp(View child) {
   2382         return HeadsUpManager.isClickedHeadsUpNotification(child);
   2383     }
   2384 
   2385     /**
   2386      * Remove a removed child view from the heads up animations if it was just added there
   2387      *
   2388      * @return whether any child was removed from the list to animate
   2389      */
   2390     private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
   2391         boolean hasAddEvent = false;
   2392         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
   2393             ExpandableNotificationRow row = eventPair.first;
   2394             boolean isHeadsUp = eventPair.second;
   2395             if (child == row) {
   2396                 mTmpList.add(eventPair);
   2397                 hasAddEvent |= isHeadsUp;
   2398             }
   2399         }
   2400         if (hasAddEvent) {
   2401             // This child was just added lets remove all events.
   2402             mHeadsUpChangeAnimations.removeAll(mTmpList);
   2403             ((ExpandableNotificationRow ) child).setHeadsupDisappearRunning(false);
   2404         }
   2405         mTmpList.clear();
   2406         return hasAddEvent;
   2407     }
   2408 
   2409     /**
   2410      * @param child the child to query
   2411      * @return whether a view is not a top level child but a child notification and that group is
   2412      *         not expanded
   2413      */
   2414     private boolean isChildInInvisibleGroup(View child) {
   2415         if (child instanceof ExpandableNotificationRow) {
   2416             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
   2417             ExpandableNotificationRow groupSummary =
   2418                     mGroupManager.getGroupSummary(row.getStatusBarNotification());
   2419             if (groupSummary != null && groupSummary != row) {
   2420                 return row.getVisibility() == View.INVISIBLE;
   2421             }
   2422         }
   2423         return false;
   2424     }
   2425 
   2426     /**
   2427      * Updates the scroll position when a child was removed
   2428      *
   2429      * @param removedChild the removed child
   2430      */
   2431     private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
   2432         int startingPosition = getPositionInLinearLayout(removedChild);
   2433         int padding = (int) NotificationUtils.interpolate(
   2434                 mPaddingBetweenElements,
   2435                 mIncreasedPaddingBetweenElements,
   2436                 removedChild.getIncreasedPaddingAmount());
   2437         int childHeight = getIntrinsicHeight(removedChild) + padding;
   2438         int endPosition = startingPosition + childHeight;
   2439         if (endPosition <= mOwnScrollY) {
   2440             // This child is fully scrolled of the top, so we have to deduct its height from the
   2441             // scrollPosition
   2442             mOwnScrollY -= childHeight;
   2443         } else if (startingPosition < mOwnScrollY) {
   2444             // This child is currently being scrolled into, set the scroll position to the start of
   2445             // this child
   2446             mOwnScrollY = startingPosition;
   2447         }
   2448     }
   2449 
   2450     private int getIntrinsicHeight(View view) {
   2451         if (view instanceof ExpandableView) {
   2452             ExpandableView expandableView = (ExpandableView) view;
   2453             return expandableView.getIntrinsicHeight();
   2454         }
   2455         return view.getHeight();
   2456     }
   2457 
   2458     private int getPositionInLinearLayout(View requestedView) {
   2459         ExpandableNotificationRow childInGroup = null;
   2460         ExpandableNotificationRow requestedRow = null;
   2461         if (isChildInGroup(requestedView)) {
   2462             // We're asking for a child in a group. Calculate the position of the parent first,
   2463             // then within the parent.
   2464             childInGroup = (ExpandableNotificationRow) requestedView;
   2465             requestedView = requestedRow = childInGroup.getNotificationParent();
   2466         }
   2467         int position = 0;
   2468         float previousIncreasedAmount = 0.0f;
   2469         for (int i = 0; i < getChildCount(); i++) {
   2470             ExpandableView child = (ExpandableView) getChildAt(i);
   2471             boolean notGone = child.getVisibility() != View.GONE;
   2472             if (notGone) {
   2473                 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
   2474                 if (position != 0) {
   2475                     position += (int) NotificationUtils.interpolate(
   2476                             mPaddingBetweenElements,
   2477                             mIncreasedPaddingBetweenElements,
   2478                             Math.max(previousIncreasedAmount, increasedPaddingAmount));
   2479                 }
   2480                 previousIncreasedAmount = increasedPaddingAmount;
   2481             }
   2482             if (child == requestedView) {
   2483                 if (requestedRow != null) {
   2484                     position += requestedRow.getPositionOfChild(childInGroup);
   2485                 }
   2486                 return position;
   2487             }
   2488             if (notGone) {
   2489                 position += getIntrinsicHeight(child);
   2490             }
   2491         }
   2492         return 0;
   2493     }
   2494 
   2495     @Override
   2496     public void onViewAdded(View child) {
   2497         super.onViewAdded(child);
   2498         onViewAddedInternal(child);
   2499     }
   2500 
   2501     private void updateFirstAndLastBackgroundViews() {
   2502         ActivatableNotificationView firstChild = getFirstChildWithBackground();
   2503         ActivatableNotificationView lastChild = getLastChildWithBackground();
   2504         if (mAnimationsEnabled && mIsExpanded) {
   2505             mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
   2506             mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
   2507         } else {
   2508             mAnimateNextBackgroundTop = false;
   2509             mAnimateNextBackgroundBottom = false;
   2510         }
   2511         mFirstVisibleBackgroundChild = firstChild;
   2512         mLastVisibleBackgroundChild = lastChild;
   2513     }
   2514 
   2515     private void onViewAddedInternal(View child) {
   2516         updateHideSensitiveForChild(child);
   2517         ((ExpandableView) child).setOnHeightChangedListener(this);
   2518         generateAddAnimation(child, false /* fromMoreCard */);
   2519         updateAnimationState(child);
   2520         updateChronometerForChild(child);
   2521     }
   2522 
   2523     private void updateHideSensitiveForChild(View child) {
   2524         if (child instanceof ExpandableView) {
   2525             ExpandableView expandableView = (ExpandableView) child;
   2526             expandableView.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
   2527         }
   2528     }
   2529 
   2530     public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
   2531         onViewRemovedInternal(row, childrenContainer);
   2532     }
   2533 
   2534     public void notifyGroupChildAdded(View row) {
   2535         onViewAddedInternal(row);
   2536     }
   2537 
   2538     public void setAnimationsEnabled(boolean animationsEnabled) {
   2539         mAnimationsEnabled = animationsEnabled;
   2540         updateNotificationAnimationStates();
   2541     }
   2542 
   2543     private void updateNotificationAnimationStates() {
   2544         boolean running = mAnimationsEnabled || mPulsing;
   2545         int childCount = getChildCount();
   2546         for (int i = 0; i < childCount; i++) {
   2547             View child = getChildAt(i);
   2548             running &= mIsExpanded || isPinnedHeadsUp(child);
   2549             updateAnimationState(running, child);
   2550         }
   2551     }
   2552 
   2553     private void updateAnimationState(View child) {
   2554         updateAnimationState((mAnimationsEnabled || mPulsing)
   2555                 && (mIsExpanded || isPinnedHeadsUp(child)), child);
   2556     }
   2557 
   2558 
   2559     private void updateAnimationState(boolean running, View child) {
   2560         if (child instanceof ExpandableNotificationRow) {
   2561             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
   2562             row.setIconAnimationRunning(running);
   2563         }
   2564     }
   2565 
   2566     public boolean isAddOrRemoveAnimationPending() {
   2567         return mNeedsAnimation
   2568                 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
   2569     }
   2570     /**
   2571      * Generate an animation for an added child view.
   2572      *
   2573      * @param child The view to be added.
   2574      * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
   2575      */
   2576     public void generateAddAnimation(View child, boolean fromMoreCard) {
   2577         if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
   2578             // Generate Animations
   2579             mChildrenToAddAnimated.add(child);
   2580             if (fromMoreCard) {
   2581                 mFromMoreCardAdditions.add(child);
   2582             }
   2583             mNeedsAnimation = true;
   2584         }
   2585         if (isHeadsUp(child) && !mChangePositionInProgress) {
   2586             mAddedHeadsUpChildren.add(child);
   2587             mChildrenToAddAnimated.remove(child);
   2588         }
   2589     }
   2590 
   2591     /**
   2592      * Change the position of child to a new location
   2593      *
   2594      * @param child the view to change the position for
   2595      * @param newIndex the new index
   2596      */
   2597     public void changeViewPosition(View child, int newIndex) {
   2598         int currentIndex = indexOfChild(child);
   2599         if (child != null && child.getParent() == this && currentIndex != newIndex) {
   2600             mChangePositionInProgress = true;
   2601             ((ExpandableView)child).setChangingPosition(true);
   2602             removeView(child);
   2603             addView(child, newIndex);
   2604             ((ExpandableView)child).setChangingPosition(false);
   2605             mChangePositionInProgress = false;
   2606             if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
   2607                 mChildrenChangingPositions.add(child);
   2608                 mNeedsAnimation = true;
   2609             }
   2610         }
   2611     }
   2612 
   2613     private void startAnimationToState() {
   2614         if (mNeedsAnimation) {
   2615             generateChildHierarchyEvents();
   2616             mNeedsAnimation = false;
   2617         }
   2618         if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
   2619             setAnimationRunning(true);
   2620             mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
   2621                     mGoToFullShadeDelay);
   2622             mAnimationEvents.clear();
   2623             updateBackground();
   2624             updateViewShadows();
   2625         } else {
   2626             applyCurrentState();
   2627         }
   2628         mGoToFullShadeDelay = 0;
   2629     }
   2630 
   2631     private void generateChildHierarchyEvents() {
   2632         generateHeadsUpAnimationEvents();
   2633         generateChildRemovalEvents();
   2634         generateChildAdditionEvents();
   2635         generatePositionChangeEvents();
   2636         generateSnapBackEvents();
   2637         generateDragEvents();
   2638         generateTopPaddingEvent();
   2639         generateActivateEvent();
   2640         generateDimmedEvent();
   2641         generateHideSensitiveEvent();
   2642         generateDarkEvent();
   2643         generateGoToFullShadeEvent();
   2644         generateViewResizeEvent();
   2645         generateGroupExpansionEvent();
   2646         generateAnimateEverythingEvent();
   2647         mNeedsAnimation = false;
   2648     }
   2649 
   2650     private void generateHeadsUpAnimationEvents() {
   2651         for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
   2652             ExpandableNotificationRow row = eventPair.first;
   2653             boolean isHeadsUp = eventPair.second;
   2654             int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
   2655             boolean onBottom = false;
   2656             boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
   2657             if (!mIsExpanded && !isHeadsUp) {
   2658                 type = row.wasJustClicked()
   2659                         ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
   2660                         : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
   2661                 if (row.isChildInGroup()) {
   2662                     // We can otherwise get stuck in there if it was just isolated
   2663                     row.setHeadsupDisappearRunning(false);
   2664                 }
   2665             } else {
   2666                 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
   2667                 if (viewState == null) {
   2668                     // A view state was never generated for this view, so we don't need to animate
   2669                     // this. This may happen with notification children.
   2670                     continue;
   2671                 }
   2672                 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
   2673                     if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
   2674                         // Our custom add animation
   2675                         type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
   2676                     } else {
   2677                         // Normal add animation
   2678                         type = AnimationEvent.ANIMATION_TYPE_ADD;
   2679                     }
   2680                     onBottom = !pinnedAndClosed;
   2681                 }
   2682             }
   2683             AnimationEvent event = new AnimationEvent(row, type);
   2684             event.headsUpFromBottom = onBottom;
   2685             mAnimationEvents.add(event);
   2686         }
   2687         mHeadsUpChangeAnimations.clear();
   2688         mAddedHeadsUpChildren.clear();
   2689     }
   2690 
   2691     private boolean shouldHunAppearFromBottom(StackViewState viewState) {
   2692         if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
   2693             return false;
   2694         }
   2695         return true;
   2696     }
   2697 
   2698     private void generateGroupExpansionEvent() {
   2699         // Generate a group expansion/collapsing event if there is such a group at all
   2700         if (mExpandedGroupView != null) {
   2701             mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
   2702                     AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
   2703             mExpandedGroupView = null;
   2704         }
   2705     }
   2706 
   2707     private void generateViewResizeEvent() {
   2708         if (mNeedViewResizeAnimation) {
   2709             mAnimationEvents.add(
   2710                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
   2711         }
   2712         mNeedViewResizeAnimation = false;
   2713     }
   2714 
   2715     private void generateSnapBackEvents() {
   2716         for (View child : mSnappedBackChildren) {
   2717             mAnimationEvents.add(new AnimationEvent(child,
   2718                     AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
   2719         }
   2720         mSnappedBackChildren.clear();
   2721     }
   2722 
   2723     private void generateDragEvents() {
   2724         for (View child : mDragAnimPendingChildren) {
   2725             mAnimationEvents.add(new AnimationEvent(child,
   2726                     AnimationEvent.ANIMATION_TYPE_START_DRAG));
   2727         }
   2728         mDragAnimPendingChildren.clear();
   2729     }
   2730 
   2731     private void generateChildRemovalEvents() {
   2732         for (View child : mChildrenToRemoveAnimated) {
   2733             boolean childWasSwipedOut = mSwipedOutViews.contains(child);
   2734             int animationType = childWasSwipedOut
   2735                     ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
   2736                     : AnimationEvent.ANIMATION_TYPE_REMOVE;
   2737             AnimationEvent event = new AnimationEvent(child, animationType);
   2738 
   2739             // we need to know the view after this one
   2740             event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
   2741             mAnimationEvents.add(event);
   2742             mSwipedOutViews.remove(child);
   2743         }
   2744         mChildrenToRemoveAnimated.clear();
   2745     }
   2746 
   2747     private void generatePositionChangeEvents() {
   2748         for (View child : mChildrenChangingPositions) {
   2749             mAnimationEvents.add(new AnimationEvent(child,
   2750                     AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
   2751         }
   2752         mChildrenChangingPositions.clear();
   2753         if (mGenerateChildOrderChangedEvent) {
   2754             mAnimationEvents.add(new AnimationEvent(null,
   2755                     AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
   2756             mGenerateChildOrderChangedEvent = false;
   2757         }
   2758     }
   2759 
   2760     private void generateChildAdditionEvents() {
   2761         for (View child : mChildrenToAddAnimated) {
   2762             if (mFromMoreCardAdditions.contains(child)) {
   2763                 mAnimationEvents.add(new AnimationEvent(child,
   2764                         AnimationEvent.ANIMATION_TYPE_ADD,
   2765                         StackStateAnimator.ANIMATION_DURATION_STANDARD));
   2766             } else {
   2767                 mAnimationEvents.add(new AnimationEvent(child,
   2768                         AnimationEvent.ANIMATION_TYPE_ADD));
   2769             }
   2770         }
   2771         mChildrenToAddAnimated.clear();
   2772         mFromMoreCardAdditions.clear();
   2773     }
   2774 
   2775     private void generateTopPaddingEvent() {
   2776         if (mTopPaddingNeedsAnimation) {
   2777             mAnimationEvents.add(
   2778                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
   2779         }
   2780         mTopPaddingNeedsAnimation = false;
   2781     }
   2782 
   2783     private void generateActivateEvent() {
   2784         if (mActivateNeedsAnimation) {
   2785             mAnimationEvents.add(
   2786                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
   2787         }
   2788         mActivateNeedsAnimation = false;
   2789     }
   2790 
   2791     private void generateAnimateEverythingEvent() {
   2792         if (mEverythingNeedsAnimation) {
   2793             mAnimationEvents.add(
   2794                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
   2795         }
   2796         mEverythingNeedsAnimation = false;
   2797     }
   2798 
   2799     private void generateDimmedEvent() {
   2800         if (mDimmedNeedsAnimation) {
   2801             mAnimationEvents.add(
   2802                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
   2803         }
   2804         mDimmedNeedsAnimation = false;
   2805     }
   2806 
   2807     private void generateHideSensitiveEvent() {
   2808         if (mHideSensitiveNeedsAnimation) {
   2809             mAnimationEvents.add(
   2810                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
   2811         }
   2812         mHideSensitiveNeedsAnimation = false;
   2813     }
   2814 
   2815     private void generateDarkEvent() {
   2816         if (mDarkNeedsAnimation) {
   2817             AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
   2818             ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
   2819             mAnimationEvents.add(ev);
   2820             startBackgroundFadeIn();
   2821         }
   2822         mDarkNeedsAnimation = false;
   2823     }
   2824 
   2825     private void generateGoToFullShadeEvent() {
   2826         if (mGoToFullShadeNeedsAnimation) {
   2827             mAnimationEvents.add(
   2828                     new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
   2829         }
   2830         mGoToFullShadeNeedsAnimation = false;
   2831     }
   2832 
   2833     private boolean onInterceptTouchEventScroll(MotionEvent ev) {
   2834         if (!isScrollingEnabled()) {
   2835             return false;
   2836         }
   2837         /*
   2838          * This method JUST determines whether we want to intercept the motion.
   2839          * If we return true, onMotionEvent will be called and we do the actual
   2840          * scrolling there.
   2841          */
   2842 
   2843         /*
   2844         * Shortcut the most recurring case: the user is in the dragging
   2845         * state and is moving their finger.  We want to intercept this
   2846         * motion.
   2847         */
   2848         final int action = ev.getAction();
   2849         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
   2850             return true;
   2851         }
   2852 
   2853         switch (action & MotionEvent.ACTION_MASK) {
   2854             case MotionEvent.ACTION_MOVE: {
   2855                 /*
   2856                  * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
   2857                  * whether the user has moved far enough from the original down touch.
   2858                  */
   2859 
   2860                 /*
   2861                 * Locally do absolute value. mLastMotionY is set to the y value
   2862                 * of the down event.
   2863                 */
   2864                 final int activePointerId = mActivePointerId;
   2865                 if (activePointerId == INVALID_POINTER) {
   2866                     // If we don't have a valid id, the touch down wasn't on content.
   2867                     break;
   2868                 }
   2869 
   2870                 final int pointerIndex = ev.findPointerIndex(activePointerId);
   2871                 if (pointerIndex == -1) {
   2872                     Log.e(TAG, "Invalid pointerId=" + activePointerId
   2873                             + " in onInterceptTouchEvent");
   2874                     break;
   2875                 }
   2876 
   2877                 final int y = (int) ev.getY(pointerIndex);
   2878                 final int x = (int) ev.getX(pointerIndex);
   2879                 final int yDiff = Math.abs(y - mLastMotionY);
   2880                 final int xDiff = Math.abs(x - mDownX);
   2881                 if (yDiff > mTouchSlop && yDiff > xDiff) {
   2882                     setIsBeingDragged(true);
   2883                     mLastMotionY = y;
   2884                     mDownX = x;
   2885                     initVelocityTrackerIfNotExists();
   2886                     mVelocityTracker.addMovement(ev);
   2887                 }
   2888                 break;
   2889             }
   2890 
   2891             case MotionEvent.ACTION_DOWN: {
   2892                 final int y = (int) ev.getY();
   2893                 mScrolledToTopOnFirstDown = isScrolledToTop();
   2894                 if (getChildAtPosition(ev.getX(), y) == null) {
   2895                     setIsBeingDragged(false);
   2896                     recycleVelocityTracker();
   2897                     break;
   2898                 }
   2899 
   2900                 /*
   2901                  * Remember location of down touch.
   2902                  * ACTION_DOWN always refers to pointer index 0.
   2903                  */
   2904                 mLastMotionY = y;
   2905                 mDownX = (int) ev.getX();
   2906                 mActivePointerId = ev.getPointerId(0);
   2907 
   2908                 initOrResetVelocityTracker();
   2909                 mVelocityTracker.addMovement(ev);
   2910                 /*
   2911                 * If being flinged and user touches the screen, initiate drag;
   2912                 * otherwise don't.  mScroller.isFinished should be false when
   2913                 * being flinged.
   2914                 */
   2915                 boolean isBeingDragged = !mScroller.isFinished();
   2916                 setIsBeingDragged(isBeingDragged);
   2917                 break;
   2918             }
   2919 
   2920             case MotionEvent.ACTION_CANCEL:
   2921             case MotionEvent.ACTION_UP:
   2922                 /* Release the drag */
   2923                 setIsBeingDragged(false);
   2924                 mActivePointerId = INVALID_POINTER;
   2925                 recycleVelocityTracker();
   2926                 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
   2927                     postInvalidateOnAnimation();
   2928                 }
   2929                 break;
   2930             case MotionEvent.ACTION_POINTER_UP:
   2931                 onSecondaryPointerUp(ev);
   2932                 break;
   2933         }
   2934 
   2935         /*
   2936         * The only time we want to intercept motion events is if we are in the
   2937         * drag mode.
   2938         */
   2939         return mIsBeingDragged;
   2940     }
   2941 
   2942     /**
   2943      * @return Whether the specified motion event is actually happening over the content.
   2944      */
   2945     private boolean isInContentBounds(MotionEvent event) {
   2946         return isInContentBounds(event.getY());
   2947     }
   2948 
   2949     /**
   2950      * @return Whether a y coordinate is inside the content.
   2951      */
   2952     public boolean isInContentBounds(float y) {
   2953         return y < getHeight() - getEmptyBottomMargin();
   2954     }
   2955 
   2956     private void setIsBeingDragged(boolean isDragged) {
   2957         mIsBeingDragged = isDragged;
   2958         if (isDragged) {
   2959             requestDisallowInterceptTouchEvent(true);
   2960             removeLongPressCallback();
   2961         }
   2962     }
   2963 
   2964     @Override
   2965     public void onWindowFocusChanged(boolean hasWindowFocus) {
   2966         super.onWindowFocusChanged(hasWindowFocus);
   2967         if (!hasWindowFocus) {
   2968             removeLongPressCallback();
   2969         }
   2970     }
   2971 
   2972     @Override
   2973     public void clearChildFocus(View child) {
   2974         super.clearChildFocus(child);
   2975         if (mForcedScroll == child) {
   2976             mForcedScroll = null;
   2977         }
   2978     }
   2979 
   2980     @Override
   2981     public void requestDisallowLongPress() {
   2982         removeLongPressCallback();
   2983     }
   2984 
   2985     @Override
   2986     public void requestDisallowDismiss() {
   2987         mDisallowDismissInThisMotion = true;
   2988     }
   2989 
   2990     public void removeLongPressCallback() {
   2991         mSwipeHelper.removeLongPressCallback();
   2992     }
   2993 
   2994     @Override
   2995     public boolean isScrolledToTop() {
   2996         return mOwnScrollY == 0;
   2997     }
   2998 
   2999     @Override
   3000     public boolean isScrolledToBottom() {
   3001         return mOwnScrollY >= getScrollRange();
   3002     }
   3003 
   3004     @Override
   3005     public View getHostView() {
   3006         return this;
   3007     }
   3008 
   3009     public int getEmptyBottomMargin() {
   3010         int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize
   3011                 - mBottomStackSlowDownHeight;
   3012         return Math.max(emptyMargin, 0);
   3013     }
   3014 
   3015     public float getKeyguardBottomStackSize() {
   3016         return mBottomStackPeekSize + getResources().getDimensionPixelSize(
   3017                 R.dimen.bottom_stack_slow_down_length);
   3018     }
   3019 
   3020     public void onExpansionStarted() {
   3021         mIsExpansionChanging = true;
   3022     }
   3023 
   3024     public void onExpansionStopped() {
   3025         mIsExpansionChanging = false;
   3026         if (!mIsExpanded) {
   3027             mOwnScrollY = 0;
   3028             mPhoneStatusBar.resetUserExpandedStates();
   3029 
   3030             // lets make sure nothing is in the overlay / transient anymore
   3031             clearTemporaryViews(this);
   3032             for (int i = 0; i < getChildCount(); i++) {
   3033                 ExpandableView child = (ExpandableView) getChildAt(i);
   3034                 if (child instanceof ExpandableNotificationRow) {
   3035                     ExpandableNotificationRow row = (ExpandableNotificationRow) child;
   3036                     clearTemporaryViews(row.getChildrenContainer());
   3037                 }
   3038             }
   3039         }
   3040     }
   3041 
   3042     private void clearTemporaryViews(ViewGroup viewGroup) {
   3043         while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
   3044             viewGroup.removeTransientView(viewGroup.getTransientView(0));
   3045         }
   3046         if (viewGroup != null) {
   3047             viewGroup.getOverlay().clear();
   3048         }
   3049     }
   3050 
   3051     public void onPanelTrackingStarted() {
   3052         mPanelTracking = true;
   3053     }
   3054     public void onPanelTrackingStopped() {
   3055         mPanelTracking = false;
   3056     }
   3057 
   3058     public void resetScrollPosition() {
   3059         mScroller.abortAnimation();
   3060         mOwnScrollY = 0;
   3061     }
   3062 
   3063     private void setIsExpanded(boolean isExpanded) {
   3064         boolean changed = isExpanded != mIsExpanded;
   3065         mIsExpanded = isExpanded;
   3066         mStackScrollAlgorithm.setIsExpanded(isExpanded);
   3067         if (changed) {
   3068             if (!mIsExpanded) {
   3069                 mGroupManager.collapseAllGroups();
   3070             }
   3071             updateNotificationAnimationStates();
   3072             updateChronometers();
   3073         }
   3074     }
   3075 
   3076     private void updateChronometers() {
   3077         int childCount = getChildCount();
   3078         for (int i = 0; i < childCount; i++) {
   3079             updateChronometerForChild(getChildAt(i));
   3080         }
   3081     }
   3082 
   3083     private void updateChronometerForChild(View child) {
   3084         if (child instanceof ExpandableNotificationRow) {
   3085             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
   3086             row.setChronometerRunning(mIsExpanded);
   3087         }
   3088     }
   3089 
   3090     @Override
   3091     public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
   3092         updateContentHeight();
   3093         updateScrollPositionOnExpandInBottom(view);
   3094         clampScrollPosition();
   3095         notifyHeightChangeListener(view);
   3096         if (needsAnimation) {
   3097             ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
   3098                     ? (ExpandableNotificationRow) view
   3099                     : null;
   3100             requestAnimationOnViewResize(row);
   3101         }
   3102         requestChildrenUpdate();
   3103     }
   3104 
   3105     @Override
   3106     public void onReset(ExpandableView view) {
   3107         if (mIsExpanded && mAnimationsEnabled) {
   3108             mRequestViewResizeAnimationOnLayout = true;
   3109         }
   3110         updateAnimationState(view);
   3111         updateChronometerForChild(view);
   3112     }
   3113 
   3114     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
   3115         if (view instanceof ExpandableNotificationRow) {
   3116             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
   3117             if (row.isUserLocked() && row != getFirstChildNotGone()) {
   3118                 if (row.isSummaryWithChildren()) {
   3119                     return;
   3120                 }
   3121                 // We are actually expanding this view
   3122                 float endPosition = row.getTranslationY() + row.getActualHeight();
   3123                 if (row.isChildInGroup()) {
   3124                     endPosition += row.getNotificationParent().getTranslationY();
   3125                 }
   3126                 int stackEnd = getStackEndPosition();
   3127                 if (endPosition > stackEnd) {
   3128                     mOwnScrollY += endPosition - stackEnd;
   3129                     mDisallowScrollingInThisMotion = true;
   3130                 }
   3131             }
   3132         }
   3133     }
   3134 
   3135     private int getStackEndPosition() {
   3136         return mMaxLayoutHeight - mBottomStackPeekSize - mBottomStackSlowDownHeight
   3137                 + mPaddingBetweenElements + (int) mStackTranslation;
   3138     }
   3139 
   3140     public void setOnHeightChangedListener(
   3141             ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
   3142         this.mOnHeightChangedListener = mOnHeightChangedListener;
   3143     }
   3144 
   3145     public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
   3146         mOnEmptySpaceClickListener = listener;
   3147     }
   3148 
   3149     public void onChildAnimationFinished() {
   3150         setAnimationRunning(false);
   3151         requestChildrenUpdate();
   3152         runAnimationFinishedRunnables();
   3153         clearViewOverlays();
   3154         clearHeadsUpDisappearRunning();
   3155     }
   3156 
   3157     private void clearHeadsUpDisappearRunning() {
   3158         for (int i = 0; i < getChildCount(); i++) {
   3159             View view = getChildAt(i);
   3160             if (view instanceof ExpandableNotificationRow) {
   3161                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
   3162                 row.setHeadsupDisappearRunning(false);
   3163                 if (row.isSummaryWithChildren()) {
   3164                     for (ExpandableNotificationRow child : row.getNotificationChildren()) {
   3165                         child.setHeadsupDisappearRunning(false);
   3166                     }
   3167                 }
   3168             }
   3169         }
   3170     }
   3171 
   3172     private void clearViewOverlays() {
   3173         for (View view : mClearOverlayViewsWhenFinished) {
   3174             StackStateAnimator.removeFromOverlay(view);
   3175         }
   3176     }
   3177 
   3178     private void runAnimationFinishedRunnables() {
   3179         for (Runnable runnable : mAnimationFinishedRunnables) {
   3180             runnable.run();
   3181         }
   3182         mAnimationFinishedRunnables.clear();
   3183     }
   3184 
   3185     /**
   3186      * See {@link AmbientState#setDimmed}.
   3187      */
   3188     public void setDimmed(boolean dimmed, boolean animate) {
   3189         mAmbientState.setDimmed(dimmed);
   3190         if (animate && mAnimationsEnabled) {
   3191             mDimmedNeedsAnimation = true;
   3192             mNeedsAnimation =  true;
   3193             animateDimmed(dimmed);
   3194         } else {
   3195             setDimAmount(dimmed ? 1.0f : 0.0f);
   3196         }
   3197         requestChildrenUpdate();
   3198     }
   3199 
   3200     private void setDimAmount(float dimAmount) {
   3201         mDimAmount = dimAmount;
   3202         updateBackgroundDimming();
   3203     }
   3204 
   3205     private void animateDimmed(boolean dimmed) {
   3206         if (mDimAnimator != null) {
   3207             mDimAnimator.cancel();
   3208         }
   3209         float target = dimmed ? 1.0f : 0.0f;
   3210         if (target == mDimAmount) {
   3211             return;
   3212         }
   3213         mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
   3214         mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
   3215         mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
   3216         mDimAnimator.addListener(mDimEndListener);
   3217         mDimAnimator.addUpdateListener(mDimUpdateListener);
   3218         mDimAnimator.start();
   3219     }
   3220 
   3221     public void setHideSensitive(boolean hideSensitive, boolean animate) {
   3222         if (hideSensitive != mAmbientState.isHideSensitive()) {
   3223             int childCount = getChildCount();
   3224             for (int i = 0; i < childCount; i++) {
   3225                 ExpandableView v = (ExpandableView) getChildAt(i);
   3226                 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
   3227             }
   3228             mAmbientState.setHideSensitive(hideSensitive);
   3229             if (animate && mAnimationsEnabled) {
   3230                 mHideSensitiveNeedsAnimation = true;
   3231                 mNeedsAnimation =  true;
   3232             }
   3233             requestChildrenUpdate();
   3234         }
   3235     }
   3236 
   3237     /**
   3238      * See {@link AmbientState#setActivatedChild}.
   3239      */
   3240     public void setActivatedChild(ActivatableNotificationView activatedChild) {
   3241         mAmbientState.setActivatedChild(activatedChild);
   3242         if (mAnimationsEnabled) {
   3243             mActivateNeedsAnimation = true;
   3244             mNeedsAnimation =  true;
   3245         }
   3246         requestChildrenUpdate();
   3247     }
   3248 
   3249     public ActivatableNotificationView getActivatedChild() {
   3250         return mAmbientState.getActivatedChild();
   3251     }
   3252 
   3253     private void applyCurrentState() {
   3254         mCurrentStackScrollState.apply();
   3255         if (mListener != null) {
   3256             mListener.onChildLocationsChanged(this);
   3257         }
   3258         runAnimationFinishedRunnables();
   3259         setAnimationRunning(false);
   3260         updateBackground();
   3261         updateViewShadows();
   3262     }
   3263 
   3264     private void updateViewShadows() {
   3265         // we need to work around an issue where the shadow would not cast between siblings when
   3266         // their z difference is between 0 and 0.1
   3267 
   3268         // Lefts first sort by Z difference
   3269         for (int i = 0; i < getChildCount(); i++) {
   3270             ExpandableView child = (ExpandableView) getChildAt(i);
   3271             if (child.getVisibility() != GONE) {
   3272                 mTmpSortedChildren.add(child);
   3273             }
   3274         }
   3275         Collections.sort(mTmpSortedChildren, mViewPositionComparator);
   3276 
   3277         // Now lets update the shadow for the views
   3278         ExpandableView previous = null;
   3279         for (int i = 0; i < mTmpSortedChildren.size(); i++) {
   3280             ExpandableView expandableView = mTmpSortedChildren.get(i);
   3281             float translationZ = expandableView.getTranslationZ();
   3282             float otherZ = previous == null ? translationZ : previous.getTranslationZ();
   3283             float diff = otherZ - translationZ;
   3284             if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
   3285                 // There is no fake shadow to be drawn
   3286                 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
   3287             } else {
   3288                 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
   3289                         expandableView.getTranslationY() - previous.getExtraBottomPadding();
   3290                 expandableView.setFakeShadowIntensity(
   3291                         diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
   3292                         previous.getOutlineAlpha(), (int) yLocation,
   3293                         previous.getOutlineTranslation());
   3294             }
   3295             previous = expandableView;
   3296         }
   3297 
   3298         mTmpSortedChildren.clear();
   3299     }
   3300 
   3301     public void goToFullShade(long delay) {
   3302         mDismissView.setInvisible();
   3303         mEmptyShadeView.setInvisible();
   3304         mGoToFullShadeNeedsAnimation = true;
   3305         mGoToFullShadeDelay = delay;
   3306         mNeedsAnimation = true;
   3307         requestChildrenUpdate();
   3308     }
   3309 
   3310     public void cancelExpandHelper() {
   3311         mExpandHelper.cancel();
   3312     }
   3313 
   3314     public void setIntrinsicPadding(int intrinsicPadding) {
   3315         mIntrinsicPadding = intrinsicPadding;
   3316     }
   3317 
   3318     public int getIntrinsicPadding() {
   3319         return mIntrinsicPadding;
   3320     }
   3321 
   3322     /**
   3323      * @return the y position of the first notification
   3324      */
   3325     public float getNotificationsTopY() {
   3326         return mTopPadding + getStackTranslation();
   3327     }
   3328 
   3329     @Override
   3330     public boolean shouldDelayChildPressedState() {
   3331         return true;
   3332     }
   3333 
   3334     /**
   3335      * See {@link AmbientState#setDark}.
   3336      */
   3337     public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
   3338         mAmbientState.setDark(dark);
   3339         if (animate && mAnimationsEnabled) {
   3340             mDarkNeedsAnimation = true;
   3341             mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
   3342             mNeedsAnimation =  true;
   3343             setBackgroundFadeAmount(0.0f);
   3344         } else if (!dark) {
   3345             setBackgroundFadeAmount(1.0f);
   3346         }
   3347         requestChildrenUpdate();
   3348         if (dark) {
   3349             setWillNotDraw(!DEBUG);
   3350             mScrimController.setExcludedBackgroundArea(null);
   3351         } else {
   3352             updateBackground();
   3353             setWillNotDraw(false);
   3354         }
   3355     }
   3356 
   3357     private void setBackgroundFadeAmount(float fadeAmount) {
   3358         mBackgroundFadeAmount = fadeAmount;
   3359         updateBackgroundDimming();
   3360     }
   3361 
   3362     public float getBackgroundFadeAmount() {
   3363         return mBackgroundFadeAmount;
   3364     }
   3365 
   3366     private void startBackgroundFadeIn() {
   3367         ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f);
   3368         int maxLength;
   3369         if (mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE
   3370                 || mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
   3371             maxLength = getNotGoneChildCount() - 1;
   3372         } else {
   3373             maxLength = Math.max(mDarkAnimationOriginIndex,
   3374                     getNotGoneChildCount() - mDarkAnimationOriginIndex - 1);
   3375         }
   3376         maxLength = Math.max(0, maxLength);
   3377         long delay = maxLength * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_DARK;
   3378         fadeAnimator.setStartDelay(delay);
   3379         fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
   3380         fadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
   3381         fadeAnimator.start();
   3382     }
   3383 
   3384     private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
   3385         if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
   3386             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
   3387         }
   3388         if (screenLocation.y > getBottomMostNotificationBottom()) {
   3389             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
   3390         }
   3391         View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
   3392         if (child != null) {
   3393             return getNotGoneIndex(child);
   3394         } else {
   3395             return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
   3396         }
   3397     }
   3398 
   3399     private int getNotGoneIndex(View child) {
   3400         int count = getChildCount();
   3401         int notGoneIndex = 0;
   3402         for (int i = 0; i < count; i++) {
   3403             View v = getChildAt(i);
   3404             if (child == v) {
   3405                 return notGoneIndex;
   3406             }
   3407             if (v.getVisibility() != View.GONE) {
   3408                 notGoneIndex++;
   3409             }
   3410         }
   3411         return -1;
   3412     }
   3413 
   3414     public void setDismissView(DismissView dismissView) {
   3415         int index = -1;
   3416         if (mDismissView != null) {
   3417             index = indexOfChild(mDismissView);
   3418             removeView(mDismissView);
   3419         }
   3420         mDismissView = dismissView;
   3421         addView(mDismissView, index);
   3422     }
   3423 
   3424     public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
   3425         int index = -1;
   3426         if (mEmptyShadeView != null) {
   3427             index = indexOfChild(mEmptyShadeView);
   3428             removeView(mEmptyShadeView);
   3429         }
   3430         mEmptyShadeView = emptyShadeView;
   3431         addView(mEmptyShadeView, index);
   3432     }
   3433 
   3434     public void updateEmptyShadeView(boolean visible) {
   3435         int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
   3436         int newVisibility = visible ? VISIBLE : GONE;
   3437         if (oldVisibility != newVisibility) {
   3438             if (newVisibility != GONE) {
   3439                 if (mEmptyShadeView.willBeGone()) {
   3440                     mEmptyShadeView.cancelAnimation();
   3441                 } else {
   3442                     mEmptyShadeView.setInvisible();
   3443                 }
   3444                 mEmptyShadeView.setVisibility(newVisibility);
   3445                 mEmptyShadeView.setWillBeGone(false);
   3446                 updateContentHeight();
   3447                 notifyHeightChangeListener(mEmptyShadeView);
   3448             } else {
   3449                 Runnable onFinishedRunnable = new Runnable() {
   3450                     @Override
   3451                     public void run() {
   3452                         mEmptyShadeView.setVisibility(GONE);
   3453                         mEmptyShadeView.setWillBeGone(false);
   3454                         updateContentHeight();
   3455                         notifyHeightChangeListener(mEmptyShadeView);
   3456                     }
   3457                 };
   3458                 if (mAnimationsEnabled && mIsExpanded) {
   3459                     mEmptyShadeView.setWillBeGone(true);
   3460                     mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
   3461                 } else {
   3462                     mEmptyShadeView.setInvisible();
   3463                     onFinishedRunnable.run();
   3464                 }
   3465             }
   3466         }
   3467     }
   3468 
   3469     public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
   3470         int index = -1;
   3471         if (mOverflowContainer != null) {
   3472             index = indexOfChild(mOverflowContainer);
   3473             removeView(mOverflowContainer);
   3474         }
   3475         mOverflowContainer = overFlowContainer;
   3476         addView(mOverflowContainer, index);
   3477     }
   3478 
   3479     public void updateOverflowContainerVisibility(boolean visible) {
   3480         int oldVisibility = mOverflowContainer.willBeGone() ? GONE
   3481                 : mOverflowContainer.getVisibility();
   3482         final int newVisibility = visible ? VISIBLE : GONE;
   3483         if (oldVisibility != newVisibility) {
   3484             Runnable onFinishedRunnable = new Runnable() {
   3485                 @Override
   3486                 public void run() {
   3487                     mOverflowContainer.setVisibility(newVisibility);
   3488                     mOverflowContainer.setWillBeGone(false);
   3489                     updateContentHeight();
   3490                     notifyHeightChangeListener(mOverflowContainer);
   3491                 }
   3492             };
   3493             if (!mAnimationsEnabled || !mIsExpanded) {
   3494                 mOverflowContainer.cancelAppearDrawing();
   3495                 onFinishedRunnable.run();
   3496             } else if (newVisibility != GONE) {
   3497                 mOverflowContainer.performAddAnimation(0,
   3498                         StackStateAnimator.ANIMATION_DURATION_STANDARD);
   3499                 mOverflowContainer.setVisibility(newVisibility);
   3500                 mOverflowContainer.setWillBeGone(false);
   3501                 updateContentHeight();
   3502                 notifyHeightChangeListener(mOverflowContainer);
   3503             } else {
   3504                 mOverflowContainer.performRemoveAnimation(
   3505                         StackStateAnimator.ANIMATION_DURATION_STANDARD,
   3506                         0.0f,
   3507                         onFinishedRunnable);
   3508                 mOverflowContainer.setWillBeGone(true);
   3509             }
   3510         }
   3511     }
   3512 
   3513     public void updateDismissView(boolean visible) {
   3514         int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
   3515         int newVisibility = visible ? VISIBLE : GONE;
   3516         if (oldVisibility != newVisibility) {
   3517             if (newVisibility != GONE) {
   3518                 if (mDismissView.willBeGone()) {
   3519                     mDismissView.cancelAnimation();
   3520                 } else {
   3521                     mDismissView.setInvisible();
   3522                 }
   3523                 mDismissView.setVisibility(newVisibility);
   3524                 mDismissView.setWillBeGone(false);
   3525                 updateContentHeight();
   3526                 notifyHeightChangeListener(mDismissView);
   3527             } else {
   3528                 Runnable dimissHideFinishRunnable = new Runnable() {
   3529                     @Override
   3530                     public void run() {
   3531                         mDismissView.setVisibility(GONE);
   3532                         mDismissView.setWillBeGone(false);
   3533                         updateContentHeight();
   3534                         notifyHeightChangeListener(mDismissView);
   3535                     }
   3536                 };
   3537                 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
   3538                     mDismissView.setWillBeGone(true);
   3539                     mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
   3540                 } else {
   3541                     dimissHideFinishRunnable.run();
   3542                 }
   3543             }
   3544         }
   3545     }
   3546 
   3547     public void setDismissAllInProgress(boolean dismissAllInProgress) {
   3548         mDismissAllInProgress = dismissAllInProgress;
   3549         mAmbientState.setDismissAllInProgress(dismissAllInProgress);
   3550         handleDismissAllClipping();
   3551     }
   3552 
   3553     private void handleDismissAllClipping() {
   3554         final int count = getChildCount();
   3555         boolean previousChildWillBeDismissed = false;
   3556         for (int i = 0; i < count; i++) {
   3557             ExpandableView child = (ExpandableView) getChildAt(i);
   3558             if (child.getVisibility() == GONE) {
   3559                 continue;
   3560             }
   3561             if (mDismissAllInProgress && previousChildWillBeDismissed) {
   3562                 child.setMinClipTopAmount(child.getClipTopAmount());
   3563             } else {
   3564                 child.setMinClipTopAmount(0);
   3565             }
   3566             previousChildWillBeDismissed = canChildBeDismissed(child);
   3567         }
   3568     }
   3569 
   3570     public boolean isDismissViewNotGone() {
   3571         return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
   3572     }
   3573 
   3574     public boolean isDismissViewVisible() {
   3575         return mDismissView.isVisible();
   3576     }
   3577 
   3578     public int getDismissViewHeight() {
   3579         return mDismissView.getHeight() + mPaddingBetweenElements;
   3580     }
   3581 
   3582     public int getEmptyShadeViewHeight() {
   3583         return mEmptyShadeView.getHeight();
   3584     }
   3585 
   3586     public float getBottomMostNotificationBottom() {
   3587         final int count = getChildCount();
   3588         float max = 0;
   3589         for (int childIdx = 0; childIdx < count; childIdx++) {
   3590             ExpandableView child = (ExpandableView) getChildAt(childIdx);
   3591             if (child.getVisibility() == GONE) {
   3592                 continue;
   3593             }
   3594             float bottom = child.getTranslationY() + child.getActualHeight();
   3595             if (bottom > max) {
   3596                 max = bottom;
   3597             }
   3598         }
   3599         return max + getStackTranslation();
   3600     }
   3601 
   3602     public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
   3603         this.mPhoneStatusBar = phoneStatusBar;
   3604     }
   3605 
   3606     public void setGroupManager(NotificationGroupManager groupManager) {
   3607         this.mGroupManager = groupManager;
   3608     }
   3609 
   3610     public void onGoToKeyguard() {
   3611         requestAnimateEverything();
   3612     }
   3613 
   3614     private void requestAnimateEverything() {
   3615         if (mIsExpanded && mAnimationsEnabled) {
   3616             mEverythingNeedsAnimation = true;
   3617             mNeedsAnimation = true;
   3618             requestChildrenUpdate();
   3619         }
   3620     }
   3621 
   3622     public boolean isBelowLastNotification(float touchX, float touchY) {
   3623         int childCount = getChildCount();
   3624         for (int i = childCount - 1; i >= 0; i--) {
   3625             ExpandableView child = (ExpandableView) getChildAt(i);
   3626             if (child.getVisibility() != View.GONE) {
   3627                 float childTop = child.getY();
   3628                 if (childTop > touchY) {
   3629                     // we are above a notification entirely let's abort
   3630                     return false;
   3631                 }
   3632                 boolean belowChild = touchY > childTop + child.getActualHeight();
   3633                 if (child == mDismissView) {
   3634                     if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
   3635                                     touchY - childTop)) {
   3636                         // We clicked on the dismiss button
   3637                         return false;
   3638                     }
   3639                 } else if (child == mEmptyShadeView) {
   3640                     // We arrived at the empty shade view, for which we accept all clicks
   3641                     return true;
   3642                 } else if (!belowChild){
   3643                     // We are on a child
   3644                     return false;
   3645                 }
   3646             }
   3647         }
   3648         return touchY > mTopPadding + mStackTranslation;
   3649     }
   3650 
   3651     @Override
   3652     public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
   3653         boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
   3654                 && (mIsExpanded || changedRow.isPinned());
   3655         if (animated) {
   3656             mExpandedGroupView = changedRow;
   3657             mNeedsAnimation = true;
   3658         }
   3659         changedRow.setChildrenExpanded(expanded, animated);
   3660         if (!mGroupExpandedForMeasure) {
   3661             onHeightChanged(changedRow, false /* needsAnimation */);
   3662         }
   3663         runAfterAnimationFinished(new Runnable() {
   3664             @Override
   3665             public void run() {
   3666                 changedRow.onFinishedExpansionChange();
   3667             }
   3668         });
   3669     }
   3670 
   3671     @Override
   3672     public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
   3673         mPhoneStatusBar.requestNotificationUpdate();
   3674     }
   3675 
   3676     /** @hide */
   3677     @Override
   3678     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
   3679         super.onInitializeAccessibilityEventInternal(event);
   3680         event.setScrollable(mScrollable);
   3681         event.setScrollX(mScrollX);
   3682         event.setScrollY(mOwnScrollY);
   3683         event.setMaxScrollX(mScrollX);
   3684         event.setMaxScrollY(getScrollRange());
   3685     }
   3686 
   3687     @Override
   3688     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
   3689         super.onInitializeAccessibilityNodeInfoInternal(info);
   3690         final int scrollRange = getScrollRange();
   3691         if (scrollRange > 0) {
   3692             info.setScrollable(true);
   3693             if (mScrollY > 0) {
   3694                 info.addAction(
   3695                         AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
   3696                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
   3697             }
   3698             if (mScrollY < scrollRange) {
   3699                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
   3700                 info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_DOWN);
   3701             }
   3702         }
   3703         // Talkback only listenes to scroll events of certain classes, let's make us a scrollview
   3704         info.setClassName(ScrollView.class.getName());
   3705     }
   3706 
   3707     /** @hide */
   3708     @Override
   3709     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
   3710         if (super.performAccessibilityActionInternal(action, arguments)) {
   3711             return true;
   3712         }
   3713         if (!isEnabled()) {
   3714             return false;
   3715         }
   3716         int direction = -1;
   3717         switch (action) {
   3718             case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
   3719                 // fall through
   3720             case android.R.id.accessibilityActionScrollDown:
   3721                 direction = 1;
   3722                 // fall through
   3723             case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
   3724                 // fall through
   3725             case android.R.id.accessibilityActionScrollUp:
   3726                 final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
   3727                         - mBottomStackPeekSize - mBottomStackSlowDownHeight;
   3728                 final int targetScrollY = Math.max(0,
   3729                         Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
   3730                 if (targetScrollY != mOwnScrollY) {
   3731                     mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScrollY - mOwnScrollY);
   3732                     postInvalidateOnAnimation();
   3733                     return true;
   3734                 }
   3735                 break;
   3736         }
   3737         return false;
   3738     }
   3739 
   3740     @Override
   3741     public void onGroupsChanged() {
   3742         mPhoneStatusBar.requestNotificationUpdate();
   3743     }
   3744 
   3745     public void generateChildOrderChangedEvent() {
   3746         if (mIsExpanded && mAnimationsEnabled) {
   3747             mGenerateChildOrderChangedEvent = true;
   3748             mNeedsAnimation = true;
   3749             requestChildrenUpdate();
   3750         }
   3751     }
   3752 
   3753     public void runAfterAnimationFinished(Runnable runnable) {
   3754         mAnimationFinishedRunnables.add(runnable);
   3755     }
   3756 
   3757     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
   3758         mHeadsUpManager = headsUpManager;
   3759         mAmbientState.setHeadsUpManager(headsUpManager);
   3760     }
   3761 
   3762     public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
   3763         if (mAnimationsEnabled) {
   3764             mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
   3765             mNeedsAnimation = true;
   3766             if (!mIsExpanded && !isHeadsUp) {
   3767                 row.setHeadsupDisappearRunning(true);
   3768             }
   3769             requestChildrenUpdate();
   3770         }
   3771     }
   3772 
   3773     public void setShadeExpanded(boolean shadeExpanded) {
   3774         mAmbientState.setShadeExpanded(shadeExpanded);
   3775         mStateAnimator.setShadeExpanded(shadeExpanded);
   3776     }
   3777 
   3778     /**
   3779      * Set the boundary for the bottom heads up position. The heads up will always be above this
   3780      * position.
   3781      *
   3782      * @param height the height of the screen
   3783      * @param bottomBarHeight the height of the bar on the bottom
   3784      */
   3785     public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
   3786         mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
   3787         mStateAnimator.setHeadsUpAppearHeightBottom(height);
   3788         requestChildrenUpdate();
   3789     }
   3790 
   3791     public void setTrackingHeadsUp(boolean trackingHeadsUp) {
   3792         mTrackingHeadsUp = trackingHeadsUp;
   3793     }
   3794 
   3795     public void setScrimController(ScrimController scrimController) {
   3796         mScrimController = scrimController;
   3797         mScrimController.setScrimBehindChangeRunnable(new Runnable() {
   3798             @Override
   3799             public void run() {
   3800                 updateBackgroundDimming();
   3801             }
   3802         });
   3803     }
   3804 
   3805     public void forceNoOverlappingRendering(boolean force) {
   3806         mForceNoOverlappingRendering = force;
   3807     }
   3808 
   3809     @Override
   3810     public boolean hasOverlappingRendering() {
   3811         return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
   3812     }
   3813 
   3814     public void setAnimationRunning(boolean animationRunning) {
   3815         if (animationRunning != mAnimationRunning) {
   3816             if (animationRunning) {
   3817                 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
   3818             } else {
   3819                 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
   3820             }
   3821             mAnimationRunning = animationRunning;
   3822             updateContinuousShadowDrawing();
   3823         }
   3824     }
   3825 
   3826     public boolean isExpanded() {
   3827         return mIsExpanded;
   3828     }
   3829 
   3830     public void setPulsing(boolean pulsing) {
   3831         mPulsing = pulsing;
   3832         updateNotificationAnimationStates();
   3833     }
   3834 
   3835     public void setFadingOut(boolean fadingOut) {
   3836         if (fadingOut != mFadingOut) {
   3837             mFadingOut = fadingOut;
   3838             updateFadingState();
   3839         }
   3840     }
   3841 
   3842     public void setParentFadingOut(boolean fadingOut) {
   3843         if (fadingOut != mParentFadingOut) {
   3844             mParentFadingOut = fadingOut;
   3845             updateFadingState();
   3846         }
   3847     }
   3848 
   3849     private void updateFadingState() {
   3850         if (mFadingOut || mParentFadingOut || mAmbientState.isDark()) {
   3851             mScrimController.setExcludedBackgroundArea(null);
   3852         } else {
   3853             applyCurrentBackgroundBounds();
   3854         }
   3855         updateSrcDrawing();
   3856     }
   3857 
   3858     @Override
   3859     public void setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) {
   3860         super.setAlpha(alpha);
   3861         setFadingOut(alpha != 1.0f);
   3862     }
   3863 
   3864     /**
   3865      * Remove the a given view from the viewstate. This is currently used when the children are
   3866      * kept in the parent artificially to have a nicer animation.
   3867      * @param view the view to remove
   3868      */
   3869     public void removeViewStateForView(View view) {
   3870         mCurrentStackScrollState.removeViewStateForView(view);
   3871     }
   3872 
   3873     /**
   3874      * A listener that is notified when some child locations might have changed.
   3875      */
   3876     public interface OnChildLocationsChangedListener {
   3877         public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
   3878     }
   3879 
   3880     /**
   3881      * A listener that is notified when the empty space below the notifications is clicked on
   3882      */
   3883     public interface OnEmptySpaceClickListener {
   3884         public void onEmptySpaceClicked(float x, float y);
   3885     }
   3886 
   3887     /**
   3888      * A listener that gets notified when the overscroll at the top has changed.
   3889      */
   3890     public interface OnOverscrollTopChangedListener {
   3891 
   3892         /**
   3893          * Notifies a listener that the overscroll has changed.
   3894          *
   3895          * @param amount the amount of overscroll, in pixels
   3896          * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
   3897          *                     unrubberbanded motion to directly expand overscroll view (e.g expand
   3898          *                     QS)
   3899          */
   3900         public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
   3901 
   3902         /**
   3903          * Notify a listener that the scroller wants to escape from the scrolling motion and
   3904          * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
   3905          *
   3906          * @param velocity The velocity that the Scroller had when over flinging
   3907          * @param open Should the fling open or close the overscroll view.
   3908          */
   3909         public void flingTopOverscroll(float velocity, boolean open);
   3910     }
   3911 
   3912     private class NotificationSwipeHelper extends SwipeHelper {
   3913         private static final long SHOW_GEAR_DELAY = 60;
   3914         private static final long COVER_GEAR_DELAY = 4000;
   3915         private CheckForDrag mCheckForDrag;
   3916         private Runnable mFalsingCheck;
   3917         private Handler mHandler;
   3918         private boolean mGearSnappedTo;
   3919         private boolean mGearSnappedOnLeft;
   3920 
   3921         public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
   3922             super(swipeDirection, callback, context);
   3923             mHandler = new Handler();
   3924             mFalsingCheck = new Runnable() {
   3925                 @Override
   3926                 public void run() {
   3927                     resetExposedGearView(true /* animate */, true /* force */);
   3928                 }
   3929             };
   3930         }
   3931 
   3932         @Override
   3933         public void onDownUpdate(View currView) {
   3934             // Set the active view
   3935             mTranslatingParentView = currView;
   3936 
   3937             // Reset check for drag gesture
   3938             cancelCheckForDrag();
   3939             if (mCurrIconRow != null) {
   3940                 mCurrIconRow.setSnapping(false);
   3941             }
   3942             mCheckForDrag = null;
   3943             mCurrIconRow = null;
   3944             mHandler.removeCallbacks(mFalsingCheck);
   3945 
   3946             // Slide back any notifications that might be showing a gear
   3947             resetExposedGearView(true /* animate */, false /* force */);
   3948 
   3949             if (currView instanceof ExpandableNotificationRow) {
   3950                 // Set the listener for the current row's gear
   3951                 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
   3952                 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
   3953             }
   3954         }
   3955 
   3956         @Override
   3957         public void onMoveUpdate(View view, float translation, float delta) {
   3958             mHandler.removeCallbacks(mFalsingCheck);
   3959 
   3960             if (mCurrIconRow != null) {
   3961                 mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
   3962 
   3963                 // If the gear is visible and the movement is towards it it's not a location change.
   3964                 boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
   3965                 boolean locationChange = isTowardsGear(translation, onLeft)
   3966                         ? false : mCurrIconRow.isIconLocationChange(translation);
   3967                 if (locationChange) {
   3968                     // Don't consider it "snapped" if location has changed.
   3969                     setSnappedToGear(false);
   3970 
   3971                     // Changed directions, make sure we check to fade in icon again.
   3972                     if (!mHandler.hasCallbacks(mCheckForDrag)) {
   3973                         // No check scheduled, set null to schedule a new one.
   3974                         mCheckForDrag = null;
   3975                     } else {
   3976                         // Check scheduled, reset alpha and update location; check will fade it in
   3977                         mCurrIconRow.setGearAlpha(0f);
   3978                         mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
   3979                     }
   3980                 }
   3981             }
   3982 
   3983             final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
   3984                     && ((ExpandableNotificationRow) view).areGutsExposed();
   3985 
   3986             if (!isPinnedHeadsUp(view) && !gutsExposed) {
   3987                 // Only show the gear if we're not a heads up view and guts aren't exposed.
   3988                 checkForDrag();
   3989             }
   3990         }
   3991 
   3992         @Override
   3993         public void dismissChild(final View view, float velocity,
   3994                 boolean useAccelerateInterpolator) {
   3995             super.dismissChild(view, velocity, useAccelerateInterpolator);
   3996             if (mIsExpanded) {
   3997                 // We don't want to quick-dismiss when it's a heads up as this might lead to closing
   3998                 // of the panel early.
   3999                 handleChildDismissed(view);
   4000             }
   4001             handleGearCoveredOrDismissed();
   4002         }
   4003 
   4004         @Override
   4005         public void snapChild(final View animView, final float targetLeft, float velocity) {
   4006             super.snapChild(animView, targetLeft, velocity);
   4007             onDragCancelled(animView);
   4008             if (targetLeft == 0) {
   4009                 handleGearCoveredOrDismissed();
   4010             }
   4011         }
   4012 
   4013         private void handleGearCoveredOrDismissed() {
   4014             cancelCheckForDrag();
   4015             setSnappedToGear(false);
   4016             if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
   4017                 mGearExposedView = null;
   4018             }
   4019         }
   4020 
   4021         @Override
   4022         public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
   4023                 float translation) {
   4024             if (mCurrIconRow == null) {
   4025                 cancelCheckForDrag();
   4026                 return false; // Let SwipeHelper handle it.
   4027             }
   4028 
   4029             boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
   4030             boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
   4031 
   4032             if (mGearSnappedTo && mCurrIconRow.isVisible()) {
   4033                 if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
   4034                     boolean coveringGear =
   4035                             Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
   4036                     if (gestureTowardsGear || coveringGear) {
   4037                         // Gesture is towards or covering the gear
   4038                         snapChild(animView, 0 /* leftTarget */, velocity);
   4039                     } else if (isDismissGesture(ev)) {
   4040                         // Gesture is a dismiss that's not towards the gear
   4041                         dismissChild(animView, velocity,
   4042                                 !swipedFastEnough() /* useAccelerateInterpolator */);
   4043                     } else {
   4044                         // Didn't move enough to dismiss or cover, snap to the gear
   4045                         snapToGear(animView, velocity);
   4046                     }
   4047                 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
   4048                         || (gestureTowardsGear && !swipedFarEnough())) {
   4049                     // The gear has been snapped to previously, however, the gear is now on the
   4050                     // other side. If gesture is towards gear and not too far snap to the gear.
   4051                     snapToGear(animView, velocity);
   4052                 } else {
   4053                     dismissOrSnapBack(animView, velocity, ev);
   4054                 }
   4055             } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
   4056                     || gestureTowardsGear) {
   4057                 // Gear has not been snapped to previously and this is gear revealing gesture
   4058                 snapToGear(animView, velocity);
   4059             } else {
   4060                 dismissOrSnapBack(animView, velocity, ev);
   4061             }
   4062             return true;
   4063         }
   4064 
   4065         private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
   4066             if (isDismissGesture(ev)) {
   4067                 dismissChild(animView, velocity,
   4068                         !swipedFastEnough() /* useAccelerateInterpolator */);
   4069             } else {
   4070                 snapChild(animView, 0 /* leftTarget */, velocity);
   4071             }
   4072         }
   4073 
   4074         private void snapToGear(View animView, float velocity) {
   4075             final float snapBackThreshold = getSpaceForGear(animView);
   4076             final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
   4077                     : -snapBackThreshold;
   4078             mGearExposedView = mTranslatingParentView;
   4079             if (animView instanceof ExpandableNotificationRow) {
   4080                 MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
   4081                         ((ExpandableNotificationRow) animView).getStatusBarNotification()
   4082                                 .getPackageName());
   4083             }
   4084             if (mCurrIconRow != null) {
   4085                 mCurrIconRow.setSnapping(true);
   4086                 setSnappedToGear(true);
   4087             }
   4088             onDragCancelled(animView);
   4089 
   4090             // If we're on the lockscreen we want to false this.
   4091             if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
   4092                 mHandler.removeCallbacks(mFalsingCheck);
   4093                 mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
   4094             }
   4095             super.snapChild(animView, target, velocity);
   4096         }
   4097 
   4098         private boolean swipedEnoughToShowGear(View animView) {
   4099             if (mTranslatingParentView == null) {
   4100                 return false;
   4101             }
   4102             // If the notification can't be dismissed then how far it can move is
   4103             // restricted -- reduce the distance it needs to move in this case.
   4104             final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
   4105             final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
   4106             final float translation = getTranslation(animView);
   4107             final boolean fromLeft = translation > 0;
   4108             final float absTrans = Math.abs(translation);
   4109             final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
   4110 
   4111             return mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
   4112                     ? (translation > snapBackThreshold && translation <= notiThreshold)
   4113                     : (translation < -snapBackThreshold && translation >= -notiThreshold));
   4114         }
   4115 
   4116         @Override
   4117         public Animator getViewTranslationAnimator(View v, float target,
   4118                 AnimatorUpdateListener listener) {
   4119             if (v instanceof ExpandableNotificationRow) {
   4120                 return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
   4121             } else {
   4122                 return super.getViewTranslationAnimator(v, target, listener);
   4123             }
   4124         }
   4125 
   4126         @Override
   4127         public void setTranslation(View v, float translate) {
   4128             ((ExpandableView) v).setTranslation(translate);
   4129         }
   4130 
   4131         @Override
   4132         public float getTranslation(View v) {
   4133             return ((ExpandableView) v).getTranslation();
   4134         }
   4135 
   4136         public void closeControlsIfOutsideTouch(MotionEvent ev) {
   4137             NotificationGuts guts = mPhoneStatusBar.getExposedGuts();
   4138             View view = null;
   4139             int height = 0;
   4140             if (guts != null) {
   4141                 // Checking guts
   4142                 view = guts;
   4143                 height = guts.getActualHeight();
   4144             } else if (mCurrIconRow != null && mCurrIconRow.isVisible()
   4145                     && mTranslatingParentView != null) {
   4146                 // Checking gear
   4147                 view = mTranslatingParentView;
   4148                 height = ((ExpandableView) mTranslatingParentView).getActualHeight();
   4149             }
   4150             if (view != null) {
   4151                 final int rx = (int) ev.getRawX();
   4152                 final int ry = (int) ev.getRawY();
   4153 
   4154                 getLocationOnScreen(mTempInt2);
   4155                 int[] location = new int[2];
   4156                 view.getLocationOnScreen(location);
   4157                 final int x = location[0] - mTempInt2[0];
   4158                 final int y = location[1] - mTempInt2[1];
   4159                 Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
   4160                 if (!rect.contains((int) rx, (int) ry)) {
   4161                     // Touch was outside visible guts / gear notification, close what's visible
   4162                     mPhoneStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
   4163                 }
   4164             }
   4165         }
   4166 
   4167         /**
   4168          * Returns whether the gesture is towards the gear location or not.
   4169          */
   4170         private boolean isTowardsGear(float velocity, boolean onLeft) {
   4171             if (mCurrIconRow == null) {
   4172                 return false;
   4173             }
   4174             return mCurrIconRow.isVisible()
   4175                     && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
   4176         }
   4177 
   4178         /**
   4179          * Indicates the the gear has been snapped to.
   4180          */
   4181         private void setSnappedToGear(boolean snapped) {
   4182             mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
   4183             mGearSnappedTo = snapped && mCurrIconRow != null;
   4184         }
   4185 
   4186         /**
   4187          * Returns the horizontal space in pixels required to display the gear behind a
   4188          * notification.
   4189          */
   4190         private float getSpaceForGear(View view) {
   4191             if (view instanceof ExpandableNotificationRow) {
   4192                 return ((ExpandableNotificationRow) view).getSpaceForGear();
   4193             }
   4194             return 0;
   4195         }
   4196 
   4197         private void checkForDrag() {
   4198             if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
   4199                 mCheckForDrag = new CheckForDrag();
   4200                 mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY);
   4201             }
   4202         }
   4203 
   4204         private void cancelCheckForDrag() {
   4205             if (mCurrIconRow != null) {
   4206                 mCurrIconRow.cancelFadeAnimator();
   4207             }
   4208             mHandler.removeCallbacks(mCheckForDrag);
   4209         }
   4210 
   4211         private final class CheckForDrag implements Runnable {
   4212             @Override
   4213             public void run() {
   4214                 if (mTranslatingParentView == null) {
   4215                     return;
   4216                 }
   4217                 final float translation = getTranslation(mTranslatingParentView);
   4218                 final float absTransX = Math.abs(translation);
   4219                 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
   4220                 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
   4221                 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
   4222                         || mCurrIconRow.isIconLocationChange(translation)))
   4223                         && absTransX >= bounceBackToGearWidth * 0.4
   4224                         && absTransX < notiThreshold) {
   4225                     // Fade in the gear
   4226                     mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
   4227                             notiThreshold);
   4228                 }
   4229             }
   4230         }
   4231 
   4232         public void resetExposedGearView(boolean animate, boolean force) {
   4233             if (mGearExposedView == null
   4234                     || (!force && mGearExposedView == mTranslatingParentView)) {
   4235                 // If no gear is showing or it's showing for this view we do nothing.
   4236                 return;
   4237             }
   4238             final View prevGearExposedView = mGearExposedView;
   4239             if (animate) {
   4240                 Animator anim = getViewTranslationAnimator(prevGearExposedView,
   4241                         0 /* leftTarget */, null /* updateListener */);
   4242                 if (anim != null) {
   4243                     anim.start();
   4244                 }
   4245             } else if (mGearExposedView instanceof ExpandableNotificationRow) {
   4246                 ((ExpandableNotificationRow) mGearExposedView).resetTranslation();
   4247             }
   4248             mGearExposedView = null;
   4249             mGearSnappedTo = false;
   4250         }
   4251     }
   4252 
   4253     private void updateContinuousShadowDrawing() {
   4254         boolean continuousShadowUpdate = mAnimationRunning
   4255                 || !mAmbientState.getDraggedViews().isEmpty();
   4256         if (continuousShadowUpdate != mContinuousShadowUpdate) {
   4257             if (continuousShadowUpdate) {
   4258                 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
   4259             } else {
   4260                 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
   4261             }
   4262             mContinuousShadowUpdate = continuousShadowUpdate;
   4263         }
   4264     }
   4265 
   4266     public void resetExposedGearView(boolean animate, boolean force) {
   4267         mSwipeHelper.resetExposedGearView(animate, force);
   4268     }
   4269 
   4270     public void closeControlsIfOutsideTouch(MotionEvent ev) {
   4271         mSwipeHelper.closeControlsIfOutsideTouch(ev);
   4272     }
   4273 
   4274     static class AnimationEvent {
   4275 
   4276         static AnimationFilter[] FILTERS = new AnimationFilter[] {
   4277 
   4278                 // ANIMATION_TYPE_ADD
   4279                 new AnimationFilter()
   4280                         .animateShadowAlpha()
   4281                         .animateHeight()
   4282                         .animateTopInset()
   4283                         .animateY()
   4284                         .animateZ()
   4285                         .hasDelays(),
   4286 
   4287                 // ANIMATION_TYPE_REMOVE
   4288                 new AnimationFilter()
   4289                         .animateShadowAlpha()
   4290                         .animateHeight()
   4291                         .animateTopInset()
   4292                         .animateY()
   4293                         .animateZ()
   4294                         .hasDelays(),
   4295 
   4296                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
   4297                 new AnimationFilter()
   4298                         .animateShadowAlpha()
   4299                         .animateHeight()
   4300                         .animateTopInset()
   4301                         .animateY()
   4302                         .animateZ()
   4303                         .hasDelays(),
   4304 
   4305                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
   4306                 new AnimationFilter()
   4307                         .animateShadowAlpha()
   4308                         .animateHeight()
   4309                         .animateTopInset()
   4310                         .animateY()
   4311                         .animateDimmed()
   4312                         .animateZ(),
   4313 
   4314                 // ANIMATION_TYPE_START_DRAG
   4315                 new AnimationFilter()
   4316                         .animateShadowAlpha(),
   4317 
   4318                 // ANIMATION_TYPE_SNAP_BACK
   4319                 new AnimationFilter()
   4320                         .animateShadowAlpha()
   4321                         .animateHeight(),
   4322 
   4323                 // ANIMATION_TYPE_ACTIVATED_CHILD
   4324                 new AnimationFilter()
   4325                         .animateZ(),
   4326 
   4327                 // ANIMATION_TYPE_DIMMED
   4328                 new AnimationFilter()
   4329                         .animateDimmed(),
   4330 
   4331                 // ANIMATION_TYPE_CHANGE_POSITION
   4332                 new AnimationFilter()
   4333                         .animateAlpha() // maybe the children change positions
   4334                         .animateShadowAlpha()
   4335                         .animateHeight()
   4336                         .animateTopInset()
   4337                         .animateY()
   4338                         .animateZ(),
   4339 
   4340                 // ANIMATION_TYPE_DARK
   4341                 new AnimationFilter()
   4342                         .animateDark()
   4343                         .hasDelays(),
   4344 
   4345                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
   4346                 new AnimationFilter()
   4347                         .animateShadowAlpha()
   4348                         .animateHeight()
   4349                         .animateTopInset()
   4350                         .animateY()
   4351                         .animateDimmed()
   4352                         .animateZ()
   4353                         .hasDelays(),
   4354 
   4355                 // ANIMATION_TYPE_HIDE_SENSITIVE
   4356                 new AnimationFilter()
   4357                         .animateHideSensitive(),
   4358 
   4359                 // ANIMATION_TYPE_VIEW_RESIZE
   4360                 new AnimationFilter()
   4361                         .animateShadowAlpha()
   4362                         .animateHeight()
   4363                         .animateTopInset()
   4364                         .animateY()
   4365                         .animateZ(),
   4366 
   4367                 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
   4368                 new AnimationFilter()
   4369                         .animateAlpha()
   4370                         .animateShadowAlpha()
   4371                         .animateHeight()
   4372                         .animateTopInset()
   4373                         .animateY()
   4374                         .animateZ(),
   4375 
   4376                 // ANIMATION_TYPE_HEADS_UP_APPEAR
   4377                 new AnimationFilter()
   4378                         .animateShadowAlpha()
   4379                         .animateHeight()
   4380                         .animateTopInset()
   4381                         .animateY()
   4382                         .animateZ(),
   4383 
   4384                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
   4385                 new AnimationFilter()
   4386                         .animateShadowAlpha()
   4387                         .animateHeight()
   4388                         .animateTopInset()
   4389                         .animateY()
   4390                         .animateZ(),
   4391 
   4392                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
   4393                 new AnimationFilter()
   4394                         .animateShadowAlpha()
   4395                         .animateHeight()
   4396                         .animateTopInset()
   4397                         .animateY()
   4398                         .animateZ()
   4399                         .hasDelays(),
   4400 
   4401                 // ANIMATION_TYPE_HEADS_UP_OTHER
   4402                 new AnimationFilter()
   4403                         .animateShadowAlpha()
   4404                         .animateHeight()
   4405                         .animateTopInset()
   4406                         .animateY()
   4407                         .animateZ(),
   4408 
   4409                 // ANIMATION_TYPE_EVERYTHING
   4410                 new AnimationFilter()
   4411                         .animateAlpha()
   4412                         .animateShadowAlpha()
   4413                         .animateDark()
   4414                         .animateDimmed()
   4415                         .animateHideSensitive()
   4416                         .animateHeight()
   4417                         .animateTopInset()
   4418                         .animateY()
   4419                         .animateZ(),
   4420         };
   4421 
   4422         static int[] LENGTHS = new int[] {
   4423 
   4424                 // ANIMATION_TYPE_ADD
   4425                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
   4426 
   4427                 // ANIMATION_TYPE_REMOVE
   4428                 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
   4429 
   4430                 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
   4431                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4432 
   4433                 // ANIMATION_TYPE_TOP_PADDING_CHANGED
   4434                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4435 
   4436                 // ANIMATION_TYPE_START_DRAG
   4437                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4438 
   4439                 // ANIMATION_TYPE_SNAP_BACK
   4440                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4441 
   4442                 // ANIMATION_TYPE_ACTIVATED_CHILD
   4443                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
   4444 
   4445                 // ANIMATION_TYPE_DIMMED
   4446                 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
   4447 
   4448                 // ANIMATION_TYPE_CHANGE_POSITION
   4449                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4450 
   4451                 // ANIMATION_TYPE_DARK
   4452                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4453 
   4454                 // ANIMATION_TYPE_GO_TO_FULL_SHADE
   4455                 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
   4456 
   4457                 // ANIMATION_TYPE_HIDE_SENSITIVE
   4458                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4459 
   4460                 // ANIMATION_TYPE_VIEW_RESIZE
   4461                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4462 
   4463                 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
   4464                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4465 
   4466                 // ANIMATION_TYPE_HEADS_UP_APPEAR
   4467                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
   4468 
   4469                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
   4470                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
   4471 
   4472                 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
   4473                 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
   4474 
   4475                 // ANIMATION_TYPE_HEADS_UP_OTHER
   4476                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4477 
   4478                 // ANIMATION_TYPE_EVERYTHING
   4479                 StackStateAnimator.ANIMATION_DURATION_STANDARD,
   4480         };
   4481 
   4482         static final int ANIMATION_TYPE_ADD = 0;
   4483         static final int ANIMATION_TYPE_REMOVE = 1;
   4484         static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
   4485         static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
   4486         static final int ANIMATION_TYPE_START_DRAG = 4;
   4487         static final int ANIMATION_TYPE_SNAP_BACK = 5;
   4488         static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
   4489         static final int ANIMATION_TYPE_DIMMED = 7;
   4490         static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
   4491         static final int ANIMATION_TYPE_DARK = 9;
   4492         static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
   4493         static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
   4494         static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
   4495         static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
   4496         static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
   4497         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
   4498         static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
   4499         static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
   4500         static final int ANIMATION_TYPE_EVERYTHING = 18;
   4501 
   4502         static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
   4503         static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
   4504 
   4505         final long eventStartTime;
   4506         final View changingView;
   4507         final int animationType;
   4508         final AnimationFilter filter;
   4509         final long length;
   4510         View viewAfterChangingView;
   4511         int darkAnimationOriginIndex;
   4512         boolean headsUpFromBottom;
   4513 
   4514         AnimationEvent(View view, int type) {
   4515             this(view, type, LENGTHS[type]);
   4516         }
   4517 
   4518         AnimationEvent(View view, int type, long length) {
   4519             eventStartTime = AnimationUtils.currentAnimationTimeMillis();
   4520             changingView = view;
   4521             animationType = type;
   4522             filter = FILTERS[type];
   4523             this.length = length;
   4524         }
   4525 
   4526         /**
   4527          * Combines the length of several animation events into a single value.
   4528          *
   4529          * @param events The events of the lengths to combine.
   4530          * @return The combined length. Depending on the event types, this might be the maximum of
   4531          *         all events or the length of a specific event.
   4532          */
   4533         static long combineLength(ArrayList<AnimationEvent> events) {
   4534             long length = 0;
   4535             int size = events.size();
   4536             for (int i = 0; i < size; i++) {
   4537                 AnimationEvent event = events.get(i);
   4538                 length = Math.max(length, event.length);
   4539                 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
   4540                     return event.length;
   4541                 }
   4542             }
   4543             return length;
   4544         }
   4545     }
   4546 
   4547 }
   4548