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