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