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