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