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