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