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