blob: a6f3bafae29dfe89c03bcdbaae35df61f854f549 [file] [log] [blame]
Selim Cinek67b22602014-03-10 15:40:16 +01001/*
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
Selim Cinek614576e2016-01-20 10:54:09 -080019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
Selim Cinekd35c2792016-01-21 13:20:57 -080023import android.animation.TimeAnimator;
24import android.animation.ValueAnimator;
Mady Mellor4b80b102016-01-22 08:03:58 -080025import android.animation.ValueAnimator.AnimatorUpdateListener;
Selim Cinek31d37b92016-04-26 09:56:42 -070026import android.annotation.FloatRange;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010027import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010028import android.content.Context;
29import android.content.res.Configuration;
Selim Cinek67b22602014-03-10 15:40:16 +010030import android.graphics.Canvas;
Selim Cinekd35c2792016-01-21 13:20:57 -080031import android.graphics.Color;
Selim Cinek67b22602014-03-10 15:40:16 +010032import android.graphics.Paint;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010033import android.graphics.PointF;
Selim Cinek6811d722016-01-19 17:53:12 -080034import android.graphics.PorterDuff;
35import android.graphics.PorterDuffXfermode;
36import android.graphics.Rect;
Mady Mellor4b80b102016-01-22 08:03:58 -080037import android.os.Handler;
Selim Cinek67b22602014-03-10 15:40:16 +010038import android.util.AttributeSet;
Selim Cinek972123d2016-05-03 14:25:58 -070039import android.util.FloatProperty;
Selim Cinek67b22602014-03-10 15:40:16 +010040import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070041import android.util.Pair;
Selim Cinek972123d2016-05-03 14:25:58 -070042import android.util.Property;
Selim Cinek67b22602014-03-10 15:40:16 +010043import android.view.MotionEvent;
44import android.view.VelocityTracker;
45import android.view.View;
46import android.view.ViewConfiguration;
47import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020048import android.view.ViewTreeObserver;
Adrian Roos5153d4a2016-03-22 10:01:56 -070049import android.view.WindowInsets;
Selim Cinek572bbd42014-04-25 16:43:27 +020050import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080051import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010052import android.widget.OverScroller;
Jorim Jaggi56306252014-07-03 00:40:09 +020053
Mady Mellora41587b2016-02-11 18:43:06 -080054import com.android.internal.logging.MetricsLogger;
55import com.android.internal.logging.MetricsProto.MetricsEvent;
Selim Cinek67b22602014-03-10 15:40:16 +010056import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080057import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010058import com.android.systemui.R;
59import com.android.systemui.SwipeHelper;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070060import com.android.systemui.classifier.FalsingManager;
Selim Cineka32ab602014-06-11 15:06:01 +020061import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040062import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020063import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010064import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020065import com.android.systemui.statusbar.ExpandableView;
Mady Mellor7a9b2a62016-03-23 07:41:47 -070066import com.android.systemui.statusbar.NotificationGuts;
Selim Cinek2cd45df2015-06-09 18:00:07 -070067import com.android.systemui.statusbar.NotificationOverflowContainer;
Mady Mellor4b80b102016-01-22 08:03:58 -080068import com.android.systemui.statusbar.NotificationSettingsIconRow;
69import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010070import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020071import com.android.systemui.statusbar.StatusBarState;
Selim Cinek33223572016-02-19 19:32:22 -080072import com.android.systemui.statusbar.notification.FakeShadowView;
Selim Cinek42357e02016-02-24 18:48:01 -080073import com.android.systemui.statusbar.notification.NotificationUtils;
Selim Cinekb5605e52015-02-20 18:21:41 +010074import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020075import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070076import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070077import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010078import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010079
Selim Cinek572bbd42014-04-25 16:43:27 +020080import java.util.ArrayList;
Selim Cinek33223572016-02-19 19:32:22 -080081import java.util.Collections;
82import java.util.Comparator;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020083import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020084
Selim Cinek67b22602014-03-10 15:40:16 +010085/**
86 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
87 */
88public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020089 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Mady Mellor4b80b102016-01-22 08:03:58 -080090 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
Adrian Roos5153d4a2016-03-22 10:01:56 -070091 SettingsIconRowListener, ScrollContainer {
Selim Cinek67b22602014-03-10 15:40:16 +010092
Selim Cinekd35c2792016-01-21 13:20:57 -080093 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -080094 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +010095 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020096 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
97 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020098 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010099 /**
100 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
101 */
102 private static final int INVALID_POINTER = -1;
103
Selim Cinek1408eb52014-06-02 14:45:38 +0200104 private ExpandHelper mExpandHelper;
Selim Cinek33223572016-02-19 19:32:22 -0800105 private NotificationSwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +0200106 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +0100107 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -0800108 private final Paint mBackgroundPaint = new Paint();
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100109
110 /**
111 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
112 * externally from {@link #setStackHeight}
113 */
114 private float mLastSetStackHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100115 private int mOwnScrollY;
116 private int mMaxLayoutHeight;
117
118 private VelocityTracker mVelocityTracker;
119 private OverScroller mScroller;
Ricky Waicd35def2016-05-03 11:07:07 +0100120 private Runnable mFinishScrollingCallback;
Selim Cinek67b22602014-03-10 15:40:16 +0100121 private int mTouchSlop;
122 private int mMinimumVelocity;
123 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100124 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200125 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100126 private boolean mIsBeingDragged;
127 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200128 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +0100129 private int mActivePointerId;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100130 private boolean mTouchIsClick;
131 private float mInitialTouchX;
132 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100133
Selim Cinek67b22602014-03-10 15:40:16 +0100134 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100135 private int mContentHeight;
136 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200137 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100138 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100139 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800140 private int mIncreasedPaddingBetweenElements;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200141 private int mTopPadding;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700142 private int mBottomInset = 0;
Selim Cinek67b22602014-03-10 15:40:16 +0100143
144 /**
145 * The algorithm which calculates the properties for our children
146 */
Muyuan Li87798022016-04-07 17:51:25 -0700147 protected final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100148
149 /**
150 * The current State this Layout is in
151 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200152 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200153 private AmbientState mAmbientState = new AmbientState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100154 private NotificationGroupManager mGroupManager;
Selim Cinek3776fe02016-02-04 13:32:43 -0800155 private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700156 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
157 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
158 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
159 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
160 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200161 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700162 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
163 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200164 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200165 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200166 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800167 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200168
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200169 /**
170 * The raw amount of the overScroll on the top, which is not rubber-banded.
171 */
172 private float mOverScrolledTopPixels;
173
174 /**
175 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
176 */
177 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200178 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200179 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200180 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100181 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200182 private boolean mNeedsAnimation;
183 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200184 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200185 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400186 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100187 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200188 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200189 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200190 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200191 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200192 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700193 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200194 private boolean mExpandingNotification;
195 private boolean mExpandedInThisMotion;
196 private boolean mScrollingEnabled;
Muyuan Li8cf63dc2016-05-18 17:40:58 -0700197 protected DismissView mDismissView;
Muyuan Lidd9ae752016-05-13 16:45:43 -0700198 protected EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400199 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200200
201 /**
202 * Was the scroller scrolled to the top when the down motion was observed?
203 */
204 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200205 /**
206 * The minimal amount of over scroll which is needed in order to switch to the quick settings
207 * when over scrolling on a expanded card.
208 */
209 private float mMinTopOverScrollToEscape;
210 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700211 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200212 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200213 private boolean mDontReportNextOverScroll;
Adrian Roos5153d4a2016-03-22 10:01:56 -0700214 private boolean mDontClampNextScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200215 private boolean mRequestViewResizeAnimationOnLayout;
216 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100217 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700218 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700219
Selim Cinek1408eb52014-06-02 14:45:38 +0200220 /**
221 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
222 * This is needed to avoid scrolling too far after the notification was collapsed in the same
223 * motion.
224 */
225 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400226 private SwipeHelper.LongPressListener mLongPressListener;
Mady Mellor4b80b102016-01-22 08:03:58 -0800227
228 private NotificationSettingsIconRow mCurrIconRow;
229 private View mTranslatingParentView;
230 private View mGearExposedView;
Selim Cinek1408eb52014-06-02 14:45:38 +0200231
232 /**
233 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
234 * animating.
235 */
236 private boolean mOnlyScrollingInThisMotion;
Adrian Roosfa139752016-04-27 09:59:08 -0700237 private boolean mDisallowDismissInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200238 private boolean mInterceptDelegateEnabled;
239 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700240 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700241 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200242 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200243 = new ViewTreeObserver.OnPreDrawListener() {
244 @Override
245 public boolean onPreDraw() {
Adrian Roos181385c2016-05-05 17:45:44 -0400246 updateForcedScroll();
Selim Cinek1f553cf2014-05-02 12:01:36 +0200247 updateChildren();
248 mChildrenUpdateRequested = false;
249 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200250 return true;
251 }
252 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200253 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100254 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100255 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700256 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek0fccc722015-07-29 17:04:36 -0700257 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700258 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
259 = new HashSet<>();
260 private HeadsUpManager mHeadsUpManager;
261 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700262 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200263 private boolean mForceNoOverlappingRendering;
Selim Cinek2cd45df2015-06-09 18:00:07 -0700264 private NotificationOverflowContainer mOverflowContainer;
Selim Cineke0890e52015-06-17 11:17:08 -0700265 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700266 private FalsingManager mFalsingManager;
Selim Cinek6811d722016-01-19 17:53:12 -0800267 private boolean mAnimationRunning;
268 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
269 = new ViewTreeObserver.OnPreDrawListener() {
270 @Override
271 public boolean onPreDraw() {
Selim Cinek614576e2016-01-20 10:54:09 -0800272 // if it needs animation
273 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
274 updateBackground();
275 }
Selim Cinek6811d722016-01-19 17:53:12 -0800276 return true;
277 }
278 };
279 private Rect mBackgroundBounds = new Rect();
Selim Cinek614576e2016-01-20 10:54:09 -0800280 private Rect mStartAnimationRect = new Rect();
281 private Rect mEndAnimationRect = new Rect();
Selim Cinekd35c2792016-01-21 13:20:57 -0800282 private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
Selim Cinek614576e2016-01-20 10:54:09 -0800283 private boolean mAnimateNextBackgroundBottom;
284 private boolean mAnimateNextBackgroundTop;
285 private ObjectAnimator mBottomAnimator = null;
286 private ObjectAnimator mTopAnimator = null;
287 private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
288 private ActivatableNotificationView mLastVisibleBackgroundChild = null;
Selim Cinekd35c2792016-01-21 13:20:57 -0800289 private int mBgColor;
290 private float mDimAmount;
291 private ValueAnimator mDimAnimator;
Selim Cinek33223572016-02-19 19:32:22 -0800292 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
Selim Cinekd35c2792016-01-21 13:20:57 -0800293 private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
294 @Override
295 public void onAnimationEnd(Animator animation) {
296 mDimAnimator = null;
297 }
298 };
299 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
300 = new ValueAnimator.AnimatorUpdateListener() {
301
302 @Override
303 public void onAnimationUpdate(ValueAnimator animation) {
304 setDimAmount((Float) animation.getAnimatedValue());
305 }
306 };
Muyuan Li4fe4a402016-03-30 16:50:11 -0700307 protected ViewGroup mQsContainer;
Selim Cinek33223572016-02-19 19:32:22 -0800308 private boolean mContinuousShadowUpdate;
309 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
310 = new ViewTreeObserver.OnPreDrawListener() {
311
312 @Override
313 public boolean onPreDraw() {
314 updateViewShadows();
315 return true;
316 }
317 };
318 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
319 @Override
320 public int compare(ExpandableView view, ExpandableView otherView) {
321 float endY = view.getTranslationY() + view.getActualHeight();
322 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
323 if (endY < otherEndY) {
324 return -1;
325 } else if (endY > otherEndY) {
326 return 1;
327 } else {
328 // The two notifications end at the same location
329 return 0;
330 }
331 }
332 };
Selim Cinek25503252016-03-03 15:31:43 -0800333 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
Selim Cinekcd5b22f2016-03-08 16:15:41 -0800334 private boolean mPulsing;
Selim Cinek31d37b92016-04-26 09:56:42 -0700335 private boolean mDrawBackgroundAsSrc;
336 private boolean mFadedOut;
Selim Cinek1b2a05e2016-04-28 14:20:39 -0700337 private boolean mGroupExpandedForMeasure;
Adrian Roos181385c2016-05-05 17:45:44 -0400338 private View mForcedScroll;
Selim Cinek972123d2016-05-03 14:25:58 -0700339 private float mBackgroundFadeAmount = 1.0f;
340 private static final Property<NotificationStackScrollLayout, Float> BACKGROUND_FADE =
341 new FloatProperty<NotificationStackScrollLayout>("backgroundFade") {
342 @Override
343 public void setValue(NotificationStackScrollLayout object, float value) {
344 object.setBackgroundFadeAmount(value);
345 }
346
347 @Override
348 public Float get(NotificationStackScrollLayout object) {
349 return object.getBackgroundFadeAmount();
350 }
351 };
Selim Cinek67b22602014-03-10 15:40:16 +0100352
353 public NotificationStackScrollLayout(Context context) {
354 this(context, null);
355 }
356
357 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
358 this(context, attrs, 0);
359 }
360
361 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
362 this(context, attrs, defStyleAttr, 0);
363 }
364
365 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
366 int defStyleRes) {
367 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinekd35c2792016-01-21 13:20:57 -0800368 mBgColor = context.getColor(R.color.notification_shade_background_color);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200369 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
370 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
371 mExpandHelper = new ExpandHelper(getContext(), this,
372 minHeight, maxHeight);
373 mExpandHelper.setEventSource(this);
374 mExpandHelper.setScrollAdapter(this);
Mady Mellor4b80b102016-01-22 08:03:58 -0800375 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
Selim Cinek1cf41c12014-08-12 20:06:19 +0200376 mSwipeHelper.setLongPressListener(mLongPressListener);
Muyuan Li333a4fc2016-04-16 17:13:46 -0700377 mStackScrollAlgorithm = createStackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100378 initView(context);
Selim Cinek6811d722016-01-19 17:53:12 -0800379 setWillNotDraw(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100380 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100381 mDebugPaint = new Paint();
382 mDebugPaint.setColor(0xffff0000);
383 mDebugPaint.setStrokeWidth(2);
384 mDebugPaint.setStyle(Paint.Style.STROKE);
385 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700386 mFalsingManager = FalsingManager.getInstance(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100387 }
388
389 @Override
Mady Mellorb53bc272016-02-11 18:28:23 -0800390 public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800391 if (mLongPressListener != null) {
Mady Mellora41587b2016-02-11 18:43:06 -0800392 MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
393 row.getStatusBarNotification().getPackageName());
Mady Mellorb53bc272016-02-11 18:28:23 -0800394 mLongPressListener.onLongPress(row, x, y);
Mady Mellor4b80b102016-01-22 08:03:58 -0800395 }
396 }
397
398 @Override
Mady Mellor7a9b2a62016-03-23 07:41:47 -0700399 public void onSettingsIconRowReset(ExpandableNotificationRow row) {
400 if (mTranslatingParentView != null && row == mTranslatingParentView) {
401 mSwipeHelper.setSnappedToGear(false);
402 mGearExposedView = null;
403 mTranslatingParentView = null;
404 }
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000405 }
406
407 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100408 protected void onDraw(Canvas canvas) {
Selim Cinekd35c2792016-01-21 13:20:57 -0800409 canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100410 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800411 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100412 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200413 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200414 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200415 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
416 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100417 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
418 y = (int) getLayoutHeight();
419 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200420 y = getHeight() - getEmptyBottomMargin();
421 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100422 }
423 }
424
Selim Cinekd35c2792016-01-21 13:20:57 -0800425 private void updateBackgroundDimming() {
426 float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
Selim Cinek972123d2016-05-03 14:25:58 -0700427 alpha *= mBackgroundFadeAmount;
Selim Cinekd35c2792016-01-21 13:20:57 -0800428 // We need to manually blend in the background color
429 int scrimColor = mScrimController.getScrimBehindColor();
430 // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
431 float alphaInv = 1 - alpha;
432 int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
Selim Cinek972123d2016-05-03 14:25:58 -0700433 (int) (mBackgroundFadeAmount * Color.red(mBgColor)
434 + alphaInv * Color.red(scrimColor)),
435 (int) (mBackgroundFadeAmount * Color.green(mBgColor)
436 + alphaInv * Color.green(scrimColor)),
437 (int) (mBackgroundFadeAmount * Color.blue(mBgColor)
438 + alphaInv * Color.blue(scrimColor)));
Selim Cinekd35c2792016-01-21 13:20:57 -0800439 mBackgroundPaint.setColor(color);
440 invalidate();
441 }
442
Selim Cinek67b22602014-03-10 15:40:16 +0100443 private void initView(Context context) {
444 mScroller = new OverScroller(getContext());
445 setFocusable(true);
446 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200447 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100448 final ViewConfiguration configuration = ViewConfiguration.get(context);
449 mTouchSlop = configuration.getScaledTouchSlop();
450 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
451 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100452 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100453 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200454 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100455 mBottomStackPeekSize = context.getResources()
456 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800457 mStackScrollAlgorithm.initView(context);
Selim Cinek61633a82016-01-25 15:54:10 -0800458 mPaddingBetweenElements = Math.max(1, context.getResources()
Selim Cinekcacc6042016-01-21 16:16:41 -0800459 .getDimensionPixelSize(R.dimen.notification_divider_height));
Selim Cinek61633a82016-01-25 15:54:10 -0800460 mIncreasedPaddingBetweenElements = context.getResources()
461 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
Selim Cinek587cbf32016-01-19 11:36:18 -0800462 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
Selim Cinek1408eb52014-06-02 14:45:38 +0200463 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
464 R.dimen.min_top_overscroll_to_qs);
Selim Cineka5eaa602014-05-12 21:27:47 +0200465 }
466
Selim Cinek25503252016-03-03 15:31:43 -0800467 public void setDrawBackgroundAsSrc(boolean asSrc) {
Selim Cinek31d37b92016-04-26 09:56:42 -0700468 mDrawBackgroundAsSrc = asSrc;
469 updateSrcDrawing();
470 }
471
472 private void updateSrcDrawing() {
473 mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && !mFadedOut ? mSrcMode : null);
Selim Cinek25503252016-03-03 15:31:43 -0800474 invalidate();
475 }
476
Selim Cinekaef92ef2014-06-06 18:06:04 +0200477 private void notifyHeightChangeListener(ExpandableView view) {
478 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100479 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200480 }
Selim Cinek67b22602014-03-10 15:40:16 +0100481 }
482
483 @Override
484 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
485 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinekfa760d42016-05-10 15:50:53 -0400486 // We need to measure all children even the GONE ones, such that the heights are calculated
487 // correctly as they are used to calculate how many we can fit on the screen.
488 final int size = getChildCount();
489 for (int i = 0; i < size; i++) {
490 measureChild(getChildAt(i), widthMeasureSpec, heightMeasureSpec);
491 }
Selim Cinek67b22602014-03-10 15:40:16 +0100492 }
493
494 @Override
495 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Selim Cinek67b22602014-03-10 15:40:16 +0100496 // we layout all our children centered on the top
497 float centerX = getWidth() / 2.0f;
498 for (int i = 0; i < getChildCount(); i++) {
499 View child = getChildAt(i);
Selim Cinekfa760d42016-05-10 15:50:53 -0400500 // We need to layout all children even the GONE ones, such that the heights are
501 // calculated correctly as they are used to calculate how many we can fit on the screen
Selim Cinek67b22602014-03-10 15:40:16 +0100502 float width = child.getMeasuredWidth();
503 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100504 child.layout((int) (centerX - width / 2.0f),
505 0,
506 (int) (centerX + width / 2.0f),
507 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100508 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200509 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100510 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200511 clampScrollPosition();
Selim Cinekb5605e52015-02-20 18:21:41 +0100512 if (mRequestViewResizeAnimationOnLayout) {
Selim Cinek5bc852a2015-12-21 12:19:09 -0800513 requestAnimationOnViewResize(null);
Selim Cinekb5605e52015-02-20 18:21:41 +0100514 mRequestViewResizeAnimationOnLayout = false;
515 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200516 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -0800517 updateFirstAndLastBackgroundViews();
Selim Cinek67b22602014-03-10 15:40:16 +0100518 }
519
Selim Cinek5bc852a2015-12-21 12:19:09 -0800520 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
521 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200522 mNeedViewResizeAnimation = true;
523 mNeedsAnimation = true;
524 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200525 }
526
Selim Cinekc27437b2014-05-14 10:23:33 +0200527 public void updateSpeedBumpIndex(int newIndex) {
Selim Cinekc27437b2014-05-14 10:23:33 +0200528 mAmbientState.setSpeedBumpIndex(newIndex);
529 }
530
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200531 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
532 mListener = listener;
533 }
534
535 /**
536 * Returns the location the given child is currently rendered at.
537 *
538 * @param child the child to get the location for
Selim Cinekb036ca42015-02-20 15:56:28 +0100539 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200540 */
541 public int getChildLocation(View child) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100542 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200543 if (childViewState == null) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100544 return StackViewState.LOCATION_UNKNOWN;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200545 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100546 if (childViewState.gone) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100547 return StackViewState.LOCATION_GONE;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100548 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200549 return childViewState.location;
550 }
551
Selim Cinek67b22602014-03-10 15:40:16 +0100552 private void setMaxLayoutHeight(int maxLayoutHeight) {
553 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200554 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100555 }
556
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200557 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700558 mAmbientState.setLayoutHeight(getLayoutHeight());
559 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100560 }
561
562 /**
563 * Updates the children views according to the stack scroll algorithm. Call this whenever
564 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
565 */
566 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -0800567 updateScrollStateForAddedChildren();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200568 mAmbientState.setScrollY(mOwnScrollY);
569 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200570 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200571 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100572 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200573 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100574 }
575 }
576
Selim Cinek3776fe02016-02-04 13:32:43 -0800577 private void updateScrollStateForAddedChildren() {
578 if (mChildrenToAddAnimated.isEmpty()) {
579 return;
580 }
581 for (int i = 0; i < getChildCount(); i++) {
582 ExpandableView child = (ExpandableView) getChildAt(i);
583 if (mChildrenToAddAnimated.contains(child)) {
584 int startingPosition = getPositionInLinearLayout(child);
Selim Cinek42357e02016-02-24 18:48:01 -0800585 int padding = child.getIncreasedPaddingAmount() == 1.0f
Selim Cinek3776fe02016-02-04 13:32:43 -0800586 ? mIncreasedPaddingBetweenElements :
587 mPaddingBetweenElements;
588 int childHeight = getIntrinsicHeight(child) + padding;
589 if (startingPosition < mOwnScrollY) {
590 // This child starts off screen, so let's keep it offscreen to keep the others visible
591
592 mOwnScrollY += childHeight;
593 }
594 }
595 }
596 clampScrollPosition();
597 }
598
Adrian Roos181385c2016-05-05 17:45:44 -0400599 private void updateForcedScroll() {
600 if (mForcedScroll != null && (!mForcedScroll.hasFocus()
601 || !mForcedScroll.isAttachedToWindow())) {
602 mForcedScroll = null;
603 }
604 if (mForcedScroll != null) {
605 ExpandableView expandableView = (ExpandableView) mForcedScroll;
606 int positionInLinearLayout = getPositionInLinearLayout(expandableView);
607 int targetScroll = targetScrollForView(expandableView, positionInLinearLayout);
608
609 targetScroll = Math.max(0, Math.min(targetScroll, getScrollRange()));
610 if (mOwnScrollY < targetScroll || positionInLinearLayout < mOwnScrollY) {
611 mOwnScrollY = targetScroll;
612 }
613 }
614 }
615
Selim Cinek319bdc42014-05-01 23:01:58 +0200616 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200617 if (!mChildrenUpdateRequested) {
618 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
619 mChildrenUpdateRequested = true;
620 invalidate();
621 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200622 }
623
Selim Cinek67b22602014-03-10 15:40:16 +0100624 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200625 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100626 }
627
Selim Cinekf7a14c02014-07-07 14:01:46 +0200628 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100629 int scrollRange = getScrollRange();
630 if (scrollRange < mOwnScrollY) {
631 mOwnScrollY = scrollRange;
632 }
633 }
634
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200635 public int getTopPadding() {
636 return mTopPadding;
637 }
638
Selim Cinek1408eb52014-06-02 14:45:38 +0200639 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200640 if (mTopPadding != topPadding) {
641 mTopPadding = topPadding;
642 updateAlgorithmHeightAndPadding();
643 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200644 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200645 mTopPaddingNeedsAnimation = true;
646 mNeedsAnimation = true;
647 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200648 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200649 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200650 }
651 }
652
653 /**
654 * Update the height of the stack to a new height.
655 *
656 * @param height the new height of the stack
657 */
658 public void setStackHeight(float height) {
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100659 mLastSetStackHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200660 setIsExpanded(height > 0.0f);
661 int newStackHeight = (int) height;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -0800662 int minStackHeight = getLayoutMinHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200663 int stackHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700664 float paddingOffset;
Selim Cinek131c1e22015-05-11 19:04:49 -0700665 boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
Selim Cinek31aada42015-12-18 17:51:15 -0800666 int normalUnfoldPositionStart = trackingHeadsUp
667 ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700668 : minStackHeight;
Selim Cinek684a4422015-04-15 16:18:39 -0700669 if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
Jorim Jaggi58bef332014-11-21 18:10:32 +0100670 || getNotGoneChildCount() == 0) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700671 paddingOffset = mTopPaddingOverflow;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200672 stackHeight = newStackHeight;
673 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800674 int translationY;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -0800675 translationY = newStackHeight - normalUnfoldPositionStart;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700676 paddingOffset = translationY - mTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200677 stackHeight = (int) (height - (translationY - mTopPadding));
678 }
679 if (stackHeight != mCurrentStackHeight) {
680 mCurrentStackHeight = stackHeight;
681 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200682 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200683 }
Selim Cinekd2281152015-04-10 14:37:46 -0700684 setStackTranslation(paddingOffset);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700685 }
686
Selim Cinekd2281152015-04-10 14:37:46 -0700687 public float getStackTranslation() {
688 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700689 }
690
Selim Cinekd2281152015-04-10 14:37:46 -0700691 private void setStackTranslation(float stackTranslation) {
692 if (stackTranslation != mStackTranslation) {
693 mStackTranslation = stackTranslation;
694 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700695 requestChildrenUpdate();
696 }
Selim Cinek67b22602014-03-10 15:40:16 +0100697 }
698
699 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100700 * Get the current height of the view. This is at most the msize of the view given by a the
Selim Cinek67b22602014-03-10 15:40:16 +0100701 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
702 *
703 * @return either the layout height or the externally defined height, whichever is smaller
704 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200705 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100706 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
707 }
708
Selim Cinek816c8e42015-11-19 12:00:45 -0800709 public int getFirstItemMinHeight() {
710 final ExpandableView firstChild = getFirstChildNotGone();
711 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100712 }
713
714 public int getBottomStackPeekSize() {
715 return mBottomStackPeekSize;
716 }
717
Selim Cinekd1ad9ab2016-03-01 17:52:20 -0800718 public int getBottomStackSlowDownHeight() {
719 return mBottomStackSlowDownHeight;
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200720 }
721
Dan Sandler4247a5c2014-07-23 15:58:08 -0400722 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100723 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400724 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100725 }
726
Jason Monk16ac3772016-02-10 15:39:21 -0500727 public void setQsContainer(ViewGroup qsContainer) {
728 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +0200729 }
730
Selim Cinek67b22602014-03-10 15:40:16 +0100731 public void onChildDismissed(View v) {
Selim Cinekd1395642016-04-28 12:22:42 -0700732 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
733 if (!row.isDismissed()) {
734 handleChildDismissed(v);
735 }
736 ViewGroup transientContainer = row.getTransientContainer();
737 if (transientContainer != null) {
738 transientContainer.removeTransientView(v);
739 }
740 }
741
742 private void handleChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400743 if (mDismissAllInProgress) {
744 return;
745 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100746 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200747 if (mDragAnimPendingChildren.contains(v)) {
748 // We start the swipe and finish it in the same frame, we don't want any animation
749 // for the drag
750 mDragAnimPendingChildren.remove(v);
751 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200752 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200753 mAmbientState.onDragFinished(v);
Selim Cinek33223572016-02-19 19:32:22 -0800754 updateContinuousShadowDrawing();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700755 if (v instanceof ExpandableNotificationRow) {
756 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
757 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700758 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700759 }
760 }
Selim Cinek2a739342016-03-17 10:28:55 -0700761 performDismiss(v);
Blazej Magnowski72323322015-07-24 11:49:40 -0700762
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700763 mFalsingManager.onNotificationDismissed();
764 if (mFalsingManager.shouldEnforceBouncer()) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700765 mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
Felipe Lemeee5d6302016-04-22 16:11:19 -0700766 false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
Blazej Magnowski72323322015-07-24 11:49:40 -0700767 }
Selim Cinekeb973562014-05-02 17:07:49 +0200768 }
769
Selim Cinek2a739342016-03-17 10:28:55 -0700770 private void performDismiss(View v) {
771 if (v instanceof ExpandableNotificationRow) {
772 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek23c80342016-03-17 18:27:36 -0700773 if (mGroupManager.isOnlyChildInSuppressedGroup(row.getStatusBarNotification())) {
Selim Cinek2a739342016-03-17 10:28:55 -0700774 ExpandableNotificationRow groupSummary =
Selim Cinek23c80342016-03-17 18:27:36 -0700775 mGroupManager.getLogicalGroupSummary(row.getStatusBarNotification());
Selim Cinek2a739342016-03-17 10:28:55 -0700776 if (groupSummary.isClearable()) {
777 performDismiss(groupSummary);
778 }
779 }
Selim Cinek3f19f602016-05-02 18:01:56 -0700780 row.setDismissed(true);
Selim Cinek2a739342016-03-17 10:28:55 -0700781 }
782 final View veto = v.findViewById(R.id.veto);
783 if (veto != null && veto.getVisibility() != View.GONE) {
784 veto.performClick();
785 }
786 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
787 }
788
Selim Cinekeb973562014-05-02 17:07:49 +0200789 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800790 public void onChildSnappedBack(View animView, float targetLeft) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200791 mAmbientState.onDragFinished(animView);
Selim Cinek33223572016-02-19 19:32:22 -0800792 updateContinuousShadowDrawing();
Selim Cinekeb973562014-05-02 17:07:49 +0200793 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200794 if (mAnimationsEnabled) {
795 mSnappedBackChildren.add(animView);
796 mNeedsAnimation = true;
797 }
Selim Cinekeb973562014-05-02 17:07:49 +0200798 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200799 } else {
800 // We start the swipe and snap back in the same frame, we don't want any animation
801 mDragAnimPendingChildren.remove(animView);
802 }
Mady Mellor7a9b2a62016-03-23 07:41:47 -0700803 if (mCurrIconRow != null && targetLeft == 0) {
804 mCurrIconRow.resetState();
805 mCurrIconRow = null;
Mady Mellor4b80b102016-01-22 08:03:58 -0800806 }
Selim Cinek67b22602014-03-10 15:40:16 +0100807 }
808
Adrian Roos5d9cc662014-05-28 17:08:13 +0200809 @Override
810 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700811 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700812 mScrimController.setTopHeadsUpDragAmount(animView,
Winsonbde852d2016-04-15 19:06:54 -0700813 Math.min(Math.abs(swipeProgress / 2f - 1.0f), 1.0f));
Selim Cinekaac93252015-04-14 20:04:12 -0700814 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800815 return true; // Don't fade out the notification
Adrian Roos5d9cc662014-05-28 17:08:13 +0200816 }
817
Selim Cinek67b22602014-03-10 15:40:16 +0100818 public void onBeginDrag(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700819 mFalsingManager.onNotificatonStartDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100820 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200821 mAmbientState.onBeginDrag(v);
Selim Cinek33223572016-02-19 19:32:22 -0800822 updateContinuousShadowDrawing();
Selim Cinek131c1e22015-05-11 19:04:49 -0700823 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200824 mDragAnimPendingChildren.add(v);
825 mNeedsAnimation = true;
826 }
Selim Cinekeb973562014-05-02 17:07:49 +0200827 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100828 }
829
Selim Cinek684a4422015-04-15 16:18:39 -0700830 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700831 if (v instanceof ExpandableNotificationRow) {
832 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700833 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700834 }
835 return false;
836 }
837
838 private boolean isHeadsUp(View v) {
839 if (v instanceof ExpandableNotificationRow) {
840 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
841 return row.isHeadsUp();
842 }
843 return false;
844 }
845
Selim Cinek67b22602014-03-10 15:40:16 +0100846 public void onDragCancelled(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700847 mFalsingManager.onNotificatonStopDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100848 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100849 }
850
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700851 @Override
852 public float getFalsingThresholdFactor() {
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700853 return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700854 }
855
Mady Mellor4b80b102016-01-22 08:03:58 -0800856 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100857 public View getChildAtPosition(MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800858 View child = getChildAtPosition(ev.getX(), ev.getY());
859 if (child instanceof ExpandableNotificationRow) {
860 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
861 ExpandableNotificationRow parent = row.getNotificationParent();
Selim Cinek43d30f02016-03-04 10:51:32 -0800862 if (parent != null && parent.areChildrenExpanded()
863 && (mGearExposedView == parent
864 || (parent.getNotificationChildren().size() == 1
865 && parent.isClearable()))) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800866 // In this case the group is expanded and showing the gear for the
867 // group, further interaction should apply to the group, not any
Selim Cinek43d30f02016-03-04 10:51:32 -0800868 // child notifications so we use the parent of the child. We also do the same
869 // if we only have a single child.
870 child = parent;
Mady Mellor4b80b102016-01-22 08:03:58 -0800871 }
872 }
873 return child;
Selim Cinek67b22602014-03-10 15:40:16 +0100874 }
875
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100876 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
877 getLocationOnScreen(mTempInt2);
878 float localTouchY = touchY - mTempInt2[1];
879
880 ExpandableView closestChild = null;
881 float minDist = Float.MAX_VALUE;
882
883 // find the view closest to the location, accounting for GONE views
884 final int count = getChildCount();
885 for (int childIdx = 0; childIdx < count; childIdx++) {
886 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
887 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800888 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100889 continue;
890 }
891 float childTop = slidingChild.getTranslationY();
892 float top = childTop + slidingChild.getClipTopAmount();
893 float bottom = childTop + slidingChild.getActualHeight();
894
895 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
896 if (dist < minDist) {
897 closestChild = slidingChild;
898 minDist = dist;
899 }
900 }
901 return closestChild;
902 }
903
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200904 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100905 getLocationOnScreen(mTempInt2);
906 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100907 }
908
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200909 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100910 // find the view under the pointer, accounting for GONE views
911 final int count = getChildCount();
912 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200913 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100914 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800915 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100916 continue;
917 }
Selim Cinek89faff12014-06-19 16:29:04 -0700918 float childTop = slidingChild.getTranslationY();
919 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200920 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200921
922 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
923 // camera affordance).
924 int left = 0;
925 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100926
927 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100928 if (slidingChild instanceof ExpandableNotificationRow) {
929 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -0700930 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cinek5bc852a2015-12-21 12:19:09 -0800931 && mHeadsUpManager.getTopEntry().entry.row != row
932 && mGroupManager.getGroupSummary(
933 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
934 != row) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700935 continue;
936 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100937 return row.getViewAtPosition(touchY - childTop);
938 }
Selim Cinek67b22602014-03-10 15:40:16 +0100939 return slidingChild;
940 }
941 }
942 return null;
943 }
944
945 public boolean canChildBeExpanded(View v) {
946 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -0700947 && ((ExpandableNotificationRow) v).isExpandable()
Selim Cinek166f7c42016-03-03 16:21:37 -0800948 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
Selim Cinek67b22602014-03-10 15:40:16 +0100949 }
950
951 public void setUserExpandedChild(View v, boolean userExpanded) {
952 if (v instanceof ExpandableNotificationRow) {
Selim Cinek388df6d2015-10-22 13:25:11 -0700953 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded,
954 true /* allowChildrenExpansion */);
Selim Cinek67b22602014-03-10 15:40:16 +0100955 }
956 }
957
958 public void setUserLockedChild(View v, boolean userLocked) {
959 if (v instanceof ExpandableNotificationRow) {
960 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
961 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200962 removeLongPressCallback();
963 requestDisallowInterceptTouchEvent(true);
964 }
965
966 @Override
967 public void expansionStateChanged(boolean isExpanding) {
968 mExpandingNotification = isExpanding;
969 if (!mExpandedInThisMotion) {
970 mMaxScrollAfterExpand = mOwnScrollY;
971 mExpandedInThisMotion = true;
972 }
973 }
974
Selim Cinek1b2a05e2016-04-28 14:20:39 -0700975 @Override
976 public int getMaxExpandHeight(ExpandableView view) {
977 int maxContentHeight = view.getMaxContentHeight();
978 if (view.isSummaryWithChildren()) {
979 // Faking a measure with the group expanded to simulate how the group would look if
980 // it was. Doing a calculation here would be highly non-trivial because of the
981 // algorithm
982 mGroupExpandedForMeasure = true;
983 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
984 mGroupManager.toggleGroupExpansion(row.getStatusBarNotification());
985 row.setForceUnlocked(true);
986 mAmbientState.setLayoutHeight(mMaxLayoutHeight);
987 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
988 mAmbientState.setLayoutHeight(getLayoutHeight());
989 mGroupManager.toggleGroupExpansion(
990 row.getStatusBarNotification());
991 mGroupExpandedForMeasure = false;
992 row.setForceUnlocked(false);
993 int height = mCurrentStackScrollState.getViewStateForView(view).height;
994 return Math.min(height, maxContentHeight);
995 }
996 return maxContentHeight;
997 }
998
Selim Cinek1408eb52014-06-02 14:45:38 +0200999 public void setScrollingEnabled(boolean enable) {
1000 mScrollingEnabled = enable;
1001 }
1002
Adrian Roos181385c2016-05-05 17:45:44 -04001003 @Override
1004 public void lockScrollTo(View v) {
1005 if (mForcedScroll == v) {
1006 return;
1007 }
1008 mForcedScroll = v;
1009 scrollTo(v);
1010 }
1011
1012 @Override
Ricky Waicd35def2016-05-03 11:07:07 +01001013 public boolean scrollTo(View v) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001014 ExpandableView expandableView = (ExpandableView) v;
Adrian Roos181385c2016-05-05 17:45:44 -04001015 int targetScroll = targetScrollForView(expandableView, getPositionInLinearLayout(v));
Ricky Waicd35def2016-05-03 11:07:07 +01001016
Adrian Roos5153d4a2016-03-22 10:01:56 -07001017 if (mOwnScrollY < targetScroll) {
1018 mScroller.startScroll(mScrollX, mOwnScrollY, 0, targetScroll - mOwnScrollY);
1019 mDontReportNextOverScroll = true;
1020 postInvalidateOnAnimation();
Ricky Waicd35def2016-05-03 11:07:07 +01001021 return true;
Adrian Roos5153d4a2016-03-22 10:01:56 -07001022 }
Ricky Waicd35def2016-05-03 11:07:07 +01001023 return false;
Adrian Roos5153d4a2016-03-22 10:01:56 -07001024 }
1025
Adrian Roos181385c2016-05-05 17:45:44 -04001026 /**
1027 * @return the scroll necessary to make the bottom edge of {@param v} align with the top of
1028 * the IME.
1029 */
1030 private int targetScrollForView(ExpandableView v, int positionInLinearLayout) {
1031 return positionInLinearLayout + v.getIntrinsicHeight() +
1032 getImeInset() - getHeight() + getTopPadding();
1033 }
1034
Adrian Roos5153d4a2016-03-22 10:01:56 -07001035 @Override
1036 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
Selim Cineka424c502016-04-05 13:07:54 -07001037 mBottomInset = insets.getSystemWindowInsetBottom();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001038
1039 int range = getScrollRange();
1040 if (mOwnScrollY > range) {
1041 // HACK: We're repeatedly getting staggered insets here while the IME is
1042 // animating away. To work around that we'll wait until things have settled.
1043 removeCallbacks(mReclamp);
1044 postDelayed(mReclamp, 50);
1045 }
1046 return insets;
1047 }
1048
1049 private Runnable mReclamp = new Runnable() {
1050 @Override
1051 public void run() {
1052 int range = getScrollRange();
1053 mScroller.startScroll(mScrollX, mOwnScrollY, 0, range - mOwnScrollY);
1054 mDontReportNextOverScroll = true;
1055 mDontClampNextScroll = true;
1056 postInvalidateOnAnimation();
1057 }
1058 };
1059
Selim Cinek1408eb52014-06-02 14:45:38 +02001060 public void setExpandingEnabled(boolean enable) {
1061 mExpandHelper.setEnabled(enable);
1062 }
1063
1064 private boolean isScrollingEnabled() {
1065 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +01001066 }
1067
Selim Cinek67b22602014-03-10 15:40:16 +01001068 public boolean canChildBeDismissed(View v) {
Selim Cinek9c17b772015-07-07 20:37:09 -07001069 return StackScrollAlgorithm.canChildBeDismissed(v);
Selim Cinek67b22602014-03-10 15:40:16 +01001070 }
1071
Selim Cinek19c8c702014-08-25 22:09:19 +02001072 @Override
1073 public boolean isAntiFalsingNeeded() {
Selim Cinekcb2b6732014-09-05 16:17:22 +02001074 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +02001075 }
1076
Selim Cinekb6d85eb2014-03-28 20:21:01 +01001077 private void setSwipingInProgress(boolean isSwiped) {
1078 mSwipingInProgress = isSwiped;
1079 if(isSwiped) {
1080 requestDisallowInterceptTouchEvent(true);
1081 }
Selim Cinek67b22602014-03-10 15:40:16 +01001082 }
1083
1084 @Override
1085 protected void onConfigurationChanged(Configuration newConfig) {
1086 super.onConfigurationChanged(newConfig);
1087 float densityScale = getResources().getDisplayMetrics().density;
1088 mSwipeHelper.setDensityScale(densityScale);
1089 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
1090 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
1091 initView(getContext());
1092 }
1093
Dan Sandlereceda3d2014-07-21 15:35:01 -04001094 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07001095 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration,
1096 true /* isDismissAll */);
Selim Cinek67b22602014-03-10 15:40:16 +01001097 }
1098
Mady Mellor86889c22016-04-18 16:37:06 -07001099 public void snapViewIfNeeded(ExpandableNotificationRow child) {
dongwan0605.kim30637e42016-03-02 17:16:47 +09001100 boolean animate = mIsExpanded || isPinnedHeadsUp(child);
Mady Mellor86889c22016-04-18 16:37:06 -07001101 // If the child is showing the gear to go to settings, snap to that
1102 float targetLeft = child.getSettingsRow().isVisible() ? child.getTranslation() : 0;
1103 mSwipeHelper.snapChildIfNeeded(child, animate, targetLeft);
dongwan0605.kim30637e42016-03-02 17:16:47 +09001104 }
1105
Selim Cinek67b22602014-03-10 15:40:16 +01001106 @Override
1107 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001108 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
1109 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001110 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001111 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001112 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001113 if (isCancelOrUp) {
1114 mExpandHelper.onlyObserveMovements(false);
1115 }
1116 boolean wasExpandingBefore = mExpandingNotification;
1117 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +02001118 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
1119 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001120 dispatchDownEventToScroller(ev);
1121 }
1122 }
Selim Cinek67b22602014-03-10 15:40:16 +01001123 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -07001124 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
1125 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001126 scrollerWantsIt = onScrollTouch(ev);
1127 }
1128 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001129 if (!mIsBeingDragged
1130 && !mExpandingNotification
1131 && !mExpandedInThisMotion
Adrian Roosfa139752016-04-27 09:59:08 -07001132 && !mOnlyScrollingInThisMotion
1133 && !mDisallowDismissInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001134 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
1135 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001136 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
1137 }
1138
1139 private void dispatchDownEventToScroller(MotionEvent ev) {
1140 MotionEvent downEvent = MotionEvent.obtain(ev);
1141 downEvent.setAction(MotionEvent.ACTION_DOWN);
1142 onScrollTouch(downEvent);
1143 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +01001144 }
1145
1146 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001147 if (!isScrollingEnabled()) {
1148 return false;
1149 }
Jason Monk16ac3772016-02-10 15:39:21 -05001150 if (ev.getY() < mQsContainer.getBottom()) {
1151 return false;
1152 }
Adrian Roos181385c2016-05-05 17:45:44 -04001153 mForcedScroll = null;
Selim Cinek67b22602014-03-10 15:40:16 +01001154 initVelocityTrackerIfNotExists();
1155 mVelocityTracker.addMovement(ev);
1156
1157 final int action = ev.getAction();
1158
1159 switch (action & MotionEvent.ACTION_MASK) {
1160 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +02001161 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +01001162 return false;
1163 }
1164 boolean isBeingDragged = !mScroller.isFinished();
1165 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +01001166 /*
1167 * If being flinged and user touches, stop the fling. isFinished
1168 * will be false if being flinged.
1169 */
1170 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001171 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +01001172 }
1173
1174 // Remember where the motion event started
1175 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001176 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01001177 mActivePointerId = ev.getPointerId(0);
1178 break;
1179 }
1180 case MotionEvent.ACTION_MOVE:
1181 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1182 if (activePointerIndex == -1) {
1183 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
1184 break;
1185 }
1186
1187 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02001188 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01001189 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001190 final int xDiff = Math.abs(x - mDownX);
1191 final int yDiff = Math.abs(deltaY);
1192 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01001193 setIsBeingDragged(true);
1194 if (deltaY > 0) {
1195 deltaY -= mTouchSlop;
1196 } else {
1197 deltaY += mTouchSlop;
1198 }
1199 }
1200 if (mIsBeingDragged) {
1201 // Scroll to follow the motion event
1202 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001203 int range = getScrollRange();
1204 if (mExpandedInThisMotion) {
1205 range = Math.min(range, mMaxScrollAfterExpand);
1206 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001207
1208 float scrollAmount;
1209 if (deltaY < 0) {
1210 scrollAmount = overScrollDown(deltaY);
1211 } else {
1212 scrollAmount = overScrollUp(deltaY, range);
1213 }
Selim Cinek67b22602014-03-10 15:40:16 +01001214
1215 // Calling overScrollBy will call onOverScrolled, which
1216 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001217 if (scrollAmount != 0.0f) {
1218 // The scrolling motion could not be compensated with the
1219 // existing overScroll, we have to scroll the view
1220 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
1221 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +01001222 }
Selim Cinek67b22602014-03-10 15:40:16 +01001223 }
1224 break;
1225 case MotionEvent.ACTION_UP:
1226 if (mIsBeingDragged) {
1227 final VelocityTracker velocityTracker = mVelocityTracker;
1228 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1229 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1230
Selim Cinek1408eb52014-06-02 14:45:38 +02001231 if (shouldOverScrollFling(initialVelocity)) {
1232 onOverScrollFling(true, initialVelocity);
1233 } else {
1234 if (getChildCount() > 0) {
1235 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
1236 float currentOverScrollTop = getCurrentOverScrollAmount(true);
1237 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
1238 fling(-initialVelocity);
1239 } else {
1240 onOverScrollFling(false, initialVelocity);
1241 }
1242 } else {
1243 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
1244 getScrollRange())) {
1245 postInvalidateOnAnimation();
1246 }
Selim Cinek67b22602014-03-10 15:40:16 +01001247 }
1248 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001249 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001250 mActivePointerId = INVALID_POINTER;
1251 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +01001252 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001253
Selim Cinek67b22602014-03-10 15:40:16 +01001254 break;
1255 case MotionEvent.ACTION_CANCEL:
1256 if (mIsBeingDragged && getChildCount() > 0) {
1257 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1258 postInvalidateOnAnimation();
1259 }
1260 mActivePointerId = INVALID_POINTER;
1261 endDrag();
1262 }
1263 break;
1264 case MotionEvent.ACTION_POINTER_DOWN: {
1265 final int index = ev.getActionIndex();
1266 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +02001267 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +01001268 mActivePointerId = ev.getPointerId(index);
1269 break;
1270 }
1271 case MotionEvent.ACTION_POINTER_UP:
1272 onSecondaryPointerUp(ev);
1273 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +02001274 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +01001275 break;
1276 }
1277 return true;
1278 }
1279
Selim Cinek1408eb52014-06-02 14:45:38 +02001280 private void onOverScrollFling(boolean open, int initialVelocity) {
1281 if (mOverscrollTopChangedListener != null) {
1282 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
1283 }
1284 mDontReportNextOverScroll = true;
1285 setOverScrollAmount(0.0f, true, false);
1286 }
1287
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001288 /**
1289 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1290 *
1291 * @param deltaY The amount to scroll upwards, has to be positive.
1292 * @return The amount of scrolling to be performed by the scroller,
1293 * not handled by the overScroll amount.
1294 */
1295 private float overScrollUp(int deltaY, int range) {
1296 deltaY = Math.max(deltaY, 0);
1297 float currentTopAmount = getCurrentOverScrollAmount(true);
1298 float newTopAmount = currentTopAmount - deltaY;
1299 if (currentTopAmount > 0) {
1300 setOverScrollAmount(newTopAmount, true /* onTop */,
1301 false /* animate */);
1302 }
1303 // Top overScroll might not grab all scrolling motion,
1304 // we have to scroll as well.
1305 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1306 float newScrollY = mOwnScrollY + scrollAmount;
1307 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001308 if (!mExpandedInThisMotion) {
1309 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1310 // We overScroll on the top
1311 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1312 false /* onTop */,
1313 false /* animate */);
1314 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001315 mOwnScrollY = range;
1316 scrollAmount = 0.0f;
1317 }
1318 return scrollAmount;
1319 }
1320
1321 /**
1322 * Perform a scroll downward and adapt the overscroll amounts accordingly
1323 *
1324 * @param deltaY The amount to scroll downwards, has to be negative.
1325 * @return The amount of scrolling to be performed by the scroller,
1326 * not handled by the overScroll amount.
1327 */
1328 private float overScrollDown(int deltaY) {
1329 deltaY = Math.min(deltaY, 0);
1330 float currentBottomAmount = getCurrentOverScrollAmount(false);
1331 float newBottomAmount = currentBottomAmount + deltaY;
1332 if (currentBottomAmount > 0) {
1333 setOverScrollAmount(newBottomAmount, false /* onTop */,
1334 false /* animate */);
1335 }
1336 // Bottom overScroll might not grab all scrolling motion,
1337 // we have to scroll as well.
1338 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1339 float newScrollY = mOwnScrollY + scrollAmount;
1340 if (newScrollY < 0) {
1341 float currentTopPixels = getCurrentOverScrolledPixels(true);
1342 // We overScroll on the top
1343 setOverScrolledPixels(currentTopPixels - newScrollY,
1344 true /* onTop */,
1345 false /* animate */);
1346 mOwnScrollY = 0;
1347 scrollAmount = 0.0f;
1348 }
1349 return scrollAmount;
1350 }
1351
Selim Cinek67b22602014-03-10 15:40:16 +01001352 private void onSecondaryPointerUp(MotionEvent ev) {
1353 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1354 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1355 final int pointerId = ev.getPointerId(pointerIndex);
1356 if (pointerId == mActivePointerId) {
1357 // This was our active pointer going up. Choose a new
1358 // active pointer and adjust accordingly.
1359 // TODO: Make this decision more intelligent.
1360 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1361 mLastMotionY = (int) ev.getY(newPointerIndex);
1362 mActivePointerId = ev.getPointerId(newPointerIndex);
1363 if (mVelocityTracker != null) {
1364 mVelocityTracker.clear();
1365 }
1366 }
1367 }
1368
1369 private void initVelocityTrackerIfNotExists() {
1370 if (mVelocityTracker == null) {
1371 mVelocityTracker = VelocityTracker.obtain();
1372 }
1373 }
1374
1375 private void recycleVelocityTracker() {
1376 if (mVelocityTracker != null) {
1377 mVelocityTracker.recycle();
1378 mVelocityTracker = null;
1379 }
1380 }
1381
1382 private void initOrResetVelocityTracker() {
1383 if (mVelocityTracker == null) {
1384 mVelocityTracker = VelocityTracker.obtain();
1385 } else {
1386 mVelocityTracker.clear();
1387 }
1388 }
1389
Ricky Waicd35def2016-05-03 11:07:07 +01001390 public void setFinishScrollingCallback(Runnable runnable) {
1391 mFinishScrollingCallback = runnable;
1392 }
1393
Selim Cinek67b22602014-03-10 15:40:16 +01001394 @Override
1395 public void computeScroll() {
1396 if (mScroller.computeScrollOffset()) {
1397 // This is called at drawing time by ViewGroup.
1398 int oldX = mScrollX;
1399 int oldY = mOwnScrollY;
1400 int x = mScroller.getCurrX();
1401 int y = mScroller.getCurrY();
1402
1403 if (oldX != x || oldY != y) {
Adrian Roos5153d4a2016-03-22 10:01:56 -07001404 int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001405 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1406 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001407 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001408 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1409 }
1410 }
Selim Cinek67b22602014-03-10 15:40:16 +01001411
Adrian Roos5153d4a2016-03-22 10:01:56 -07001412 if (mDontClampNextScroll) {
1413 range = Math.max(range, oldY);
1414 }
Selim Cinek67b22602014-03-10 15:40:16 +01001415 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001416 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001417 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001418 }
1419
1420 // Keep on drawing until the animation has finished.
1421 postInvalidateOnAnimation();
Adrian Roos5153d4a2016-03-22 10:01:56 -07001422 } else {
1423 mDontClampNextScroll = false;
Ricky Waicd35def2016-05-03 11:07:07 +01001424 if (mFinishScrollingCallback != null) {
1425 mFinishScrollingCallback.run();
1426 }
Selim Cinek67b22602014-03-10 15:40:16 +01001427 }
1428 }
1429
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001430 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001431 protected boolean overScrollBy(int deltaX, int deltaY,
1432 int scrollX, int scrollY,
1433 int scrollRangeX, int scrollRangeY,
1434 int maxOverScrollX, int maxOverScrollY,
1435 boolean isTouchEvent) {
1436
1437 int newScrollY = scrollY + deltaY;
Selim Cinek4195dd02014-05-19 18:16:14 +02001438 final int top = -maxOverScrollY;
1439 final int bottom = maxOverScrollY + scrollRangeY;
1440
1441 boolean clampedY = false;
1442 if (newScrollY > bottom) {
1443 newScrollY = bottom;
1444 clampedY = true;
1445 } else if (newScrollY < top) {
1446 newScrollY = top;
1447 clampedY = true;
1448 }
1449
1450 onOverScrolled(0, newScrollY, false, clampedY);
1451
1452 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001453 }
1454
1455 /**
1456 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1457 * overscroll effect based on numPixels. By default this will also cancel animations on the
1458 * same overScroll edge.
1459 *
1460 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1461 * the rubber-banding logic.
1462 * @param onTop Should the effect be applied on top of the scroller.
1463 * @param animate Should an animation be performed.
1464 */
1465 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001466 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001467 }
1468
1469 /**
1470 * Set the effective overScroll amount which will be directly reflected in the layout.
1471 * By default this will also cancel animations on the same overScroll edge.
1472 *
1473 * @param amount The amount to overScroll by.
1474 * @param onTop Should the effect be applied on top of the scroller.
1475 * @param animate Should an animation be performed.
1476 */
1477 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1478 setOverScrollAmount(amount, onTop, animate, true);
1479 }
1480
1481 /**
1482 * Set the effective overScroll amount which will be directly reflected in the layout.
1483 *
1484 * @param amount The amount to overScroll by.
1485 * @param onTop Should the effect be applied on top of the scroller.
1486 * @param animate Should an animation be performed.
1487 * @param cancelAnimators Should running animations be cancelled.
1488 */
1489 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1490 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001491 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1492 }
1493
1494 /**
1495 * Set the effective overScroll amount which will be directly reflected in the layout.
1496 *
1497 * @param amount The amount to overScroll by.
1498 * @param onTop Should the effect be applied on top of the scroller.
1499 * @param animate Should an animation be performed.
1500 * @param cancelAnimators Should running animations be cancelled.
1501 * @param isRubberbanded The value which will be passed to
1502 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1503 */
1504 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1505 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001506 if (cancelAnimators) {
1507 mStateAnimator.cancelOverScrollAnimators(onTop);
1508 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001509 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001510 }
1511
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001512 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1513 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001514 amount = Math.max(0, amount);
1515 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001516 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001517 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001518 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001519 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001520 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001521 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001522 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001523 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001524 }
1525 }
1526
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001527 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001528 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1529 if (mDontReportNextOverScroll) {
1530 mDontReportNextOverScroll = false;
1531 return;
1532 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001533 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001534 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001535 }
1536 }
1537
1538 public void setOverscrollTopChangedListener(
1539 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1540 mOverscrollTopChangedListener = overscrollTopChangedListener;
1541 }
1542
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001543 public float getCurrentOverScrollAmount(boolean top) {
1544 return mAmbientState.getOverScrollAmount(top);
1545 }
1546
1547 public float getCurrentOverScrolledPixels(boolean top) {
1548 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1549 }
1550
1551 private void setOverScrolledPixels(float amount, boolean onTop) {
1552 if (onTop) {
1553 mOverScrolledTopPixels = amount;
1554 } else {
1555 mOverScrolledBottomPixels = amount;
1556 }
1557 }
1558
Selim Cinek319bdc42014-05-01 23:01:58 +02001559 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001560 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001561 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001562 }
1563
1564 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001565 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001566 // Treat animating scrolls differently; see #computeScroll() for why.
1567 if (!mScroller.isFinished()) {
1568 final int oldX = mScrollX;
1569 final int oldY = mOwnScrollY;
1570 mScrollX = scrollX;
1571 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001572 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001573 springBack();
1574 } else {
1575 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1576 invalidateParentIfNeeded();
1577 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001578 float overScrollTop = getCurrentOverScrollAmount(true);
1579 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001580 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001581 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001582 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001583 }
Selim Cinek67b22602014-03-10 15:40:16 +01001584 }
Selim Cinek67b22602014-03-10 15:40:16 +01001585 } else {
1586 customScrollTo(scrollY);
1587 scrollTo(scrollX, mScrollY);
1588 }
1589 }
1590
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001591 private void springBack() {
1592 int scrollRange = getScrollRange();
1593 boolean overScrolledTop = mOwnScrollY <= 0;
1594 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1595 if (overScrolledTop || overScrolledBottom) {
1596 boolean onTop;
1597 float newAmount;
1598 if (overScrolledTop) {
1599 onTop = true;
1600 newAmount = -mOwnScrollY;
1601 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001602 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001603 } else {
1604 onTop = false;
1605 newAmount = mOwnScrollY - scrollRange;
1606 mOwnScrollY = scrollRange;
1607 }
1608 setOverScrollAmount(newAmount, onTop, false);
1609 setOverScrollAmount(0.0f, onTop, true);
1610 mScroller.forceFinished(true);
1611 }
1612 }
1613
Selim Cinek67b22602014-03-10 15:40:16 +01001614 private int getScrollRange() {
Selim Cineka424c502016-04-05 13:07:54 -07001615 int contentHeight = getContentHeight();
1616 int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1617 + mBottomStackSlowDownHeight);
1618 int imeInset = getImeInset();
1619 scrollRange += Math.min(imeInset, Math.max(0,
1620 getContentHeight() - (getHeight() - imeInset)));
1621 return scrollRange;
1622 }
1623
1624 private int getImeInset() {
1625 return Math.max(0, mBottomInset - (getRootView().getHeight() - getHeight()));
Selim Cinek67b22602014-03-10 15:40:16 +01001626 }
1627
Selim Cinek343e6e22014-04-11 21:23:30 +02001628 /**
1629 * @return the first child which has visibility unequal to GONE
1630 */
Selim Cinekb55386d2015-12-16 17:26:49 -08001631 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02001632 int childCount = getChildCount();
1633 for (int i = 0; i < childCount; i++) {
1634 View child = getChildAt(i);
1635 if (child.getVisibility() != View.GONE) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001636 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02001637 }
1638 }
1639 return null;
1640 }
1641
Selim Cinek4a1ac842014-05-01 15:51:58 +02001642 /**
Selim Cinek1b2a05e2016-04-28 14:20:39 -07001643 * @return the child before the given view which has visibility unequal to GONE
1644 */
1645 public ExpandableView getViewBeforeView(ExpandableView view) {
1646 ExpandableView previousView = null;
1647 int childCount = getChildCount();
1648 for (int i = 0; i < childCount; i++) {
1649 View child = getChildAt(i);
1650 if (child == view) {
1651 return previousView;
1652 }
1653 if (child.getVisibility() != View.GONE) {
1654 previousView = (ExpandableView) child;
1655 }
1656 }
1657 return null;
1658 }
1659
1660 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001661 * @return The first child which has visibility unequal to GONE which is currently below the
1662 * given translationY or equal to it.
1663 */
1664 private View getFirstChildBelowTranlsationY(float translationY) {
1665 int childCount = getChildCount();
1666 for (int i = 0; i < childCount; i++) {
1667 View child = getChildAt(i);
1668 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1669 return child;
1670 }
1671 }
1672 return null;
1673 }
1674
1675 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001676 * @return the last child which has visibility unequal to GONE
1677 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001678 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001679 int childCount = getChildCount();
1680 for (int i = childCount - 1; i >= 0; i--) {
1681 View child = getChildAt(i);
1682 if (child.getVisibility() != View.GONE) {
1683 return child;
1684 }
1685 }
1686 return null;
1687 }
1688
Jorim Jaggi069cd032014-05-15 03:09:01 +02001689 /**
1690 * @return the number of children which have visibility unequal to GONE
1691 */
1692 public int getNotGoneChildCount() {
1693 int childCount = getChildCount();
1694 int count = 0;
1695 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07001696 ExpandableView child = (ExpandableView) getChildAt(i);
1697 if (child.getVisibility() != View.GONE && !child.willBeGone()) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02001698 count++;
1699 }
1700 }
1701 return count;
1702 }
1703
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001704 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001705 return mContentHeight;
1706 }
1707
1708 private void updateContentHeight() {
1709 int height = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08001710 float previousIncreasedAmount = 0.0f;
Selim Cinek67b22602014-03-10 15:40:16 +01001711 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08001712 ExpandableView expandableView = (ExpandableView) getChildAt(i);
1713 if (expandableView.getVisibility() != View.GONE) {
Selim Cinek42357e02016-02-24 18:48:01 -08001714 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001715 if (height != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08001716 height += (int) NotificationUtils.interpolate(
1717 mPaddingBetweenElements,
1718 mIncreasedPaddingBetweenElements,
1719 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Jorim Jaggid4a57442014-04-10 02:45:55 +02001720 }
Selim Cinek42357e02016-02-24 18:48:01 -08001721 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08001722 height += expandableView.getIntrinsicHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001723 }
1724 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001725 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001726 }
1727
Selim Cinek6811d722016-01-19 17:53:12 -08001728 private void updateBackground() {
1729 if (mAmbientState.isDark()) {
1730 return;
1731 }
1732 updateBackgroundBounds();
1733 if (!mCurrentBounds.equals(mBackgroundBounds)) {
Selim Cinek614576e2016-01-20 10:54:09 -08001734 if (mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom || areBoundsAnimating()) {
1735 startBackgroundAnimation();
1736 } else {
1737 mCurrentBounds.set(mBackgroundBounds);
1738 applyCurrentBackgroundBounds();
1739 }
1740 } else {
1741 if (mBottomAnimator != null) {
1742 mBottomAnimator.cancel();
1743 }
1744 if (mTopAnimator != null) {
1745 mTopAnimator.cancel();
1746 }
Selim Cinek6811d722016-01-19 17:53:12 -08001747 }
Selim Cinek614576e2016-01-20 10:54:09 -08001748 mAnimateNextBackgroundBottom = false;
1749 mAnimateNextBackgroundTop = false;
1750 }
1751
1752 private boolean areBoundsAnimating() {
1753 return mBottomAnimator != null || mTopAnimator != null;
1754 }
1755
1756 private void startBackgroundAnimation() {
Selim Cinek01e40192016-05-12 14:57:22 -07001757 // left and right are always instantly applied
1758 mCurrentBounds.left = mBackgroundBounds.left;
1759 mCurrentBounds.right = mBackgroundBounds.right;
Selim Cinek614576e2016-01-20 10:54:09 -08001760 startBottomAnimation();
1761 startTopAnimation();
1762 }
1763
1764 private void startTopAnimation() {
1765 int previousEndValue = mEndAnimationRect.top;
1766 int newEndValue = mBackgroundBounds.top;
1767 ObjectAnimator previousAnimator = mTopAnimator;
1768 if (previousAnimator != null && previousEndValue == newEndValue) {
1769 return;
1770 }
1771 if (!mAnimateNextBackgroundTop) {
1772 // just a local update was performed
1773 if (previousAnimator != null) {
1774 // we need to increase all animation keyframes of the previous animator by the
1775 // relative change to the end value
1776 int previousStartValue = mStartAnimationRect.top;
1777 PropertyValuesHolder[] values = previousAnimator.getValues();
1778 values[0].setIntValues(previousStartValue, newEndValue);
1779 mStartAnimationRect.top = previousStartValue;
1780 mEndAnimationRect.top = newEndValue;
1781 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1782 return;
1783 } else {
1784 // no new animation needed, let's just apply the value
1785 setBackgroundTop(newEndValue);
1786 return;
1787 }
1788 }
1789 if (previousAnimator != null) {
1790 previousAnimator.cancel();
1791 }
1792 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
1793 mCurrentBounds.top, newEndValue);
1794 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1795 animator.setInterpolator(interpolator);
1796 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1797 // remove the tag when the animation is finished
1798 animator.addListener(new AnimatorListenerAdapter() {
1799 @Override
1800 public void onAnimationEnd(Animator animation) {
1801 mStartAnimationRect.top = -1;
1802 mEndAnimationRect.top = -1;
1803 mTopAnimator = null;
1804 }
1805 });
1806 animator.start();
1807 mStartAnimationRect.top = mCurrentBounds.top;
1808 mEndAnimationRect.top = newEndValue;
1809 mTopAnimator = animator;
1810 }
1811
1812 private void startBottomAnimation() {
1813 int previousStartValue = mStartAnimationRect.bottom;
1814 int previousEndValue = mEndAnimationRect.bottom;
1815 int newEndValue = mBackgroundBounds.bottom;
1816 ObjectAnimator previousAnimator = mBottomAnimator;
1817 if (previousAnimator != null && previousEndValue == newEndValue) {
1818 return;
1819 }
1820 if (!mAnimateNextBackgroundBottom) {
1821 // just a local update was performed
1822 if (previousAnimator != null) {
1823 // we need to increase all animation keyframes of the previous animator by the
1824 // relative change to the end value
1825 PropertyValuesHolder[] values = previousAnimator.getValues();
1826 values[0].setIntValues(previousStartValue, newEndValue);
1827 mStartAnimationRect.bottom = previousStartValue;
1828 mEndAnimationRect.bottom = newEndValue;
1829 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1830 return;
1831 } else {
1832 // no new animation needed, let's just apply the value
1833 setBackgroundBottom(newEndValue);
1834 return;
1835 }
1836 }
1837 if (previousAnimator != null) {
1838 previousAnimator.cancel();
1839 }
1840 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
1841 mCurrentBounds.bottom, newEndValue);
1842 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1843 animator.setInterpolator(interpolator);
1844 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1845 // remove the tag when the animation is finished
1846 animator.addListener(new AnimatorListenerAdapter() {
1847 @Override
1848 public void onAnimationEnd(Animator animation) {
1849 mStartAnimationRect.bottom = -1;
1850 mEndAnimationRect.bottom = -1;
1851 mBottomAnimator = null;
1852 }
1853 });
1854 animator.start();
1855 mStartAnimationRect.bottom = mCurrentBounds.bottom;
1856 mEndAnimationRect.bottom = newEndValue;
1857 mBottomAnimator = animator;
1858 }
1859
1860 private void setBackgroundTop(int top) {
1861 mCurrentBounds.top = top;
1862 applyCurrentBackgroundBounds();
1863 }
1864
1865 public void setBackgroundBottom(int bottom) {
1866 mCurrentBounds.bottom = bottom;
1867 applyCurrentBackgroundBounds();
1868 }
1869
1870 private void applyCurrentBackgroundBounds() {
Selim Cinek31d37b92016-04-26 09:56:42 -07001871 if (!mFadedOut) {
1872 mScrimController.setExcludedBackgroundArea(mCurrentBounds);
1873 }
Selim Cinek614576e2016-01-20 10:54:09 -08001874 invalidate();
Selim Cinek6811d722016-01-19 17:53:12 -08001875 }
1876
1877 /**
1878 * Update the background bounds to the new desired bounds
1879 */
1880 private void updateBackgroundBounds() {
Xiaohui Chen96921b62016-05-04 16:56:25 -07001881 getLocationInWindow(mTempInt2);
1882 mBackgroundBounds.left = mTempInt2[0];
1883 mBackgroundBounds.right = mTempInt2[0] + getWidth();
Selim Cinek614576e2016-01-20 10:54:09 -08001884 if (!mIsExpanded) {
1885 mBackgroundBounds.top = 0;
1886 mBackgroundBounds.bottom = 0;
1887 }
1888 ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001889 int top = 0;
1890 if (firstView != null) {
1891 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView);
Selim Cinek614576e2016-01-20 10:54:09 -08001892 if (mAnimateNextBackgroundTop
1893 || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
1894 || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
Selim Cinek6811d722016-01-19 17:53:12 -08001895 // we're ending up at the same location as we are now, lets just skip the animation
1896 top = finalTranslationY;
1897 } else {
1898 top = (int) firstView.getTranslationY();
1899 }
1900 }
Selim Cinek614576e2016-01-20 10:54:09 -08001901 ActivatableNotificationView lastView = mLastVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001902 int bottom = 0;
1903 if (lastView != null) {
1904 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView);
1905 int finalHeight = StackStateAnimator.getFinalActualHeight(lastView);
1906 int finalBottom = finalTranslationY + finalHeight;
1907 finalBottom = Math.min(finalBottom, getHeight());
Selim Cinek614576e2016-01-20 10:54:09 -08001908 if (mAnimateNextBackgroundBottom
1909 || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
1910 || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
Selim Cinek6811d722016-01-19 17:53:12 -08001911 // we're ending up at the same location as we are now, lets just skip the animation
1912 bottom = finalBottom;
1913 } else {
1914 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
1915 bottom = Math.min(bottom, getHeight());
1916 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001917 } else {
1918 top = (int) (mTopPadding + mStackTranslation);
Selim Cinek7db42982016-02-02 15:21:41 -08001919 bottom = top;
Selim Cinek6811d722016-01-19 17:53:12 -08001920 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001921 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1922 mBackgroundBounds.top = (int) Math.max(mTopPadding + mStackTranslation, top);
1923 } else {
1924 // otherwise the animation from the shade to the keyguard will jump as it's maxed
1925 mBackgroundBounds.top = Math.max(0, top);
1926 }
1927 mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
Selim Cinek6811d722016-01-19 17:53:12 -08001928 }
1929
Selim Cinek614576e2016-01-20 10:54:09 -08001930 private ActivatableNotificationView getFirstPinnedHeadsUp() {
1931 int childCount = getChildCount();
1932 for (int i = 0; i < childCount; i++) {
1933 View child = getChildAt(i);
1934 if (child.getVisibility() != View.GONE
1935 && child instanceof ExpandableNotificationRow) {
1936 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1937 if (row.isPinned()) {
1938 return row;
1939 }
1940 }
1941 }
1942 return null;
1943 }
1944
1945 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001946 int childCount = getChildCount();
1947 for (int i = childCount - 1; i >= 0; i--) {
1948 View child = getChildAt(i);
1949 if (child.getVisibility() != View.GONE
1950 && child instanceof ActivatableNotificationView) {
1951 return (ActivatableNotificationView) child;
1952 }
1953 }
1954 return null;
1955 }
1956
Selim Cinek614576e2016-01-20 10:54:09 -08001957 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001958 int childCount = getChildCount();
1959 for (int i = 0; i < childCount; i++) {
1960 View child = getChildAt(i);
1961 if (child.getVisibility() != View.GONE
1962 && child instanceof ActivatableNotificationView) {
1963 return (ActivatableNotificationView) child;
1964 }
1965 }
1966 return null;
1967 }
1968
Selim Cinek67b22602014-03-10 15:40:16 +01001969 /**
1970 * Fling the scroll view
1971 *
1972 * @param velocityY The initial velocity in the Y direction. Positive
1973 * numbers mean that the finger/cursor is moving down the screen,
1974 * which means we want to scroll towards the top.
1975 */
Muyuan Li26e30ae2016-04-11 17:31:42 -07001976 protected void fling(int velocityY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001977 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001978 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001979
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001980 float topAmount = getCurrentOverScrollAmount(true);
1981 float bottomAmount = getCurrentOverScrollAmount(false);
1982 if (velocityY < 0 && topAmount > 0) {
1983 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001984 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001985 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001986 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001987 * mOverflingDistance + topAmount;
1988 } else if (velocityY > 0 && bottomAmount > 0) {
1989 mOwnScrollY += bottomAmount;
1990 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001991 mMaxOverScroll = Math.abs(velocityY) / 1000f
1992 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1993 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001994 } else {
1995 // it will be set once we reach the boundary
1996 mMaxOverScroll = 0.0f;
1997 }
Selim Cinek94ab18c2016-02-25 12:35:51 -08001998 int minScrollY = Math.max(0, scrollRange);
1999 if (mExpandedInThisMotion) {
2000 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
2001 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002002 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek94ab18c2016-02-25 12:35:51 -08002003 minScrollY, 0, mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01002004
2005 postInvalidateOnAnimation();
2006 }
2007 }
2008
Selim Cinek1408eb52014-06-02 14:45:38 +02002009 /**
2010 * @return Whether a fling performed on the top overscroll edge lead to the expanded
2011 * overScroll view (i.e QS).
2012 */
2013 private boolean shouldOverScrollFling(int initialVelocity) {
2014 float topOverScroll = getCurrentOverScrollAmount(true);
2015 return mScrolledToTopOnFirstDown
2016 && !mExpandedInThisMotion
2017 && topOverScroll > mMinTopOverScrollToEscape
2018 && initialVelocity > 0;
2019 }
2020
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002021 /**
2022 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
2023 * account.
2024 *
2025 * @param qsHeight the top padding imposed by the quick settings panel
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002026 * @param animate whether to animate the change
2027 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
2028 * {@code qsHeight} is the final top padding
2029 */
Jason Monk16ac3772016-02-10 15:39:21 -05002030 public void updateTopPadding(float qsHeight, boolean animate,
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002031 boolean ignoreIntrinsicPadding) {
Jason Monk16ac3772016-02-10 15:39:21 -05002032 float start = qsHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02002033 float stackHeight = getHeight() - start;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002034 int minStackHeight = getLayoutMinHeight();
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002035 if (stackHeight <= minStackHeight) {
2036 float overflow = minStackHeight - stackHeight;
2037 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02002038 start = getHeight() - stackHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002039 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02002040 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002041 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02002042 }
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01002043 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
2044 animate);
2045 setStackHeight(mLastSetStackHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02002046 }
2047
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002048 public int getLayoutMinHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08002049 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002050 int firstChildMinHeight = firstChild != null
2051 ? firstChild.getIntrinsicHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08002052 : mCollapsedSize;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002053 if (mOwnScrollY > 0) {
2054 firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
2055 }
2056 return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
2057 mMaxLayoutHeight - mTopPadding);
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002058 }
2059
2060 public float getTopPaddingOverflow() {
2061 return mTopPaddingOverflow;
2062 }
2063
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002064 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08002065 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek567e8452016-03-24 10:54:56 -07002066 final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08002067 : mCollapsedSize;
2068 return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002069 + mBottomStackSlowDownHeight;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02002070 }
2071
Selim Cinek1408eb52014-06-02 14:45:38 +02002072 private int clampPadding(int desiredPadding) {
2073 return Math.max(desiredPadding, mIntrinsicPadding);
2074 }
2075
Selim Cinekfed1ab62014-06-17 14:10:33 -07002076 private float getRubberBandFactor(boolean onTop) {
2077 if (!onTop) {
2078 return RUBBER_BAND_FACTOR_NORMAL;
2079 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002080 if (mExpandedInThisMotion) {
2081 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002082 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02002083 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
2084 } else if (mScrolledToTopOnFirstDown) {
2085 return 1.0f;
2086 }
2087 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02002088 }
2089
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002090 /**
2091 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
2092 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
2093 * overscroll view (e.g. expand QS).
2094 */
2095 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002096 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002097 || !mScrolledToTopOnFirstDown;
2098 }
2099
Selim Cinek67b22602014-03-10 15:40:16 +01002100 private void endDrag() {
2101 setIsBeingDragged(false);
2102
2103 recycleVelocityTracker();
2104
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02002105 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
2106 setOverScrollAmount(0, true /* onTop */, true /* animate */);
2107 }
2108 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
2109 setOverScrollAmount(0, false /* onTop */, true /* animate */);
2110 }
Selim Cinek67b22602014-03-10 15:40:16 +01002111 }
2112
Jorim Jaggi56306252014-07-03 00:40:09 +02002113 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
2114 ev.offsetLocation(sourceView.getX(), sourceView.getY());
2115 ev.offsetLocation(-targetView.getX(), -targetView.getY());
2116 }
2117
Selim Cinek67b22602014-03-10 15:40:16 +01002118 @Override
2119 public boolean onInterceptTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002120 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002121 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02002122 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002123 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002124 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
2125 }
Selim Cinek67b22602014-03-10 15:40:16 +01002126 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02002127 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01002128 scrollWantsIt = onInterceptTouchEventScroll(ev);
2129 }
2130 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02002131 if (!mIsBeingDragged
2132 && !mExpandingNotification
2133 && !mExpandedInThisMotion
Adrian Roosfa139752016-04-27 09:59:08 -07002134 && !mOnlyScrollingInThisMotion
2135 && !mDisallowDismissInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01002136 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
2137 }
Selim Cinek1408eb52014-06-02 14:45:38 +02002138 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
2139 }
2140
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002141 private void handleEmptySpaceClick(MotionEvent ev) {
2142 switch (ev.getActionMasked()) {
2143 case MotionEvent.ACTION_MOVE:
2144 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
2145 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
2146 mTouchIsClick = false;
2147 }
2148 break;
2149 case MotionEvent.ACTION_UP:
2150 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
2151 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
2152 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
2153 }
2154 break;
2155 }
2156 }
2157
Selim Cinek1408eb52014-06-02 14:45:38 +02002158 private void initDownStates(MotionEvent ev) {
2159 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
2160 mExpandedInThisMotion = false;
2161 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002162 mDisallowScrollingInThisMotion = false;
Adrian Roosfa139752016-04-27 09:59:08 -07002163 mDisallowDismissInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002164 mTouchIsClick = true;
2165 mInitialTouchX = ev.getX();
2166 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02002167 }
Selim Cinek67b22602014-03-10 15:40:16 +01002168 }
2169
Selim Cinekef5127e2015-12-21 16:55:58 -08002170 public void setChildTransferInProgress(boolean childTransferInProgress) {
2171 mChildTransferInProgress = childTransferInProgress;
2172 }
2173
Christoph Studer068f5922014-04-08 17:43:07 -04002174 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002175 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04002176 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002177 // we only call our internal methods if this is actually a removal and not just a
2178 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08002179 if (!mChildTransferInProgress) {
Selim Cinekd1395642016-04-28 12:22:42 -07002180 onViewRemovedInternal(child, this);
Selim Cinekb5605e52015-02-20 18:21:41 +01002181 }
2182 }
2183
Selim Cinek197823d2016-03-24 13:06:00 -07002184 @Override
2185 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
2186 super.requestDisallowInterceptTouchEvent(disallowIntercept);
2187 if (disallowIntercept) {
2188 mSwipeHelper.removeLongPressCallback();
2189 }
2190 }
2191
Selim Cineka5703182016-05-11 21:23:16 -04002192 private void onViewRemovedInternal(View child, ViewGroup container) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002193 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002194 // This is only a position change, don't do anything special
2195 return;
2196 }
Selim Cinekd1395642016-04-28 12:22:42 -07002197 ExpandableView expandableView = (ExpandableView) child;
2198 expandableView.setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04002199 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002200 updateScrollStateForRemovedChild(expandableView);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07002201 boolean animationGenerated = generateRemoveAnimation(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002202 if (animationGenerated) {
2203 if (!mSwipedOutViews.contains(child)) {
Selim Cineka5703182016-05-11 21:23:16 -04002204 container.getOverlay().add(child);
Selim Cinekd1395642016-04-28 12:22:42 -07002205 } else if (Math.abs(expandableView.getTranslation()) != expandableView.getWidth()) {
Selim Cineka5703182016-05-11 21:23:16 -04002206 container.addTransientView(child, 0);
2207 expandableView.setTransientContainer(container);
Selim Cinekd1395642016-04-28 12:22:42 -07002208 }
2209 } else {
2210 mSwipedOutViews.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002211 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002212 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02002213
2214 // Make sure the clipRect we might have set is removed
Mady Mellorc128f222016-04-26 11:42:46 -07002215 expandableView.setClipTopAmount(0);
Selim Cinekc27437b2014-05-14 10:23:33 +02002216 }
2217
Selim Cinekb5605e52015-02-20 18:21:41 +01002218 private boolean isChildInGroup(View child) {
2219 return child instanceof ExpandableNotificationRow
2220 && mGroupManager.isChildInGroupWithSummary(
2221 ((ExpandableNotificationRow) child).getStatusBarNotification());
2222 }
2223
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002224 /**
2225 * Generate a remove animation for a child view.
2226 *
2227 * @param child The view to generate the remove animation for.
2228 * @return Whether an animation was generated.
2229 */
2230 private boolean generateRemoveAnimation(View child) {
Selim Cineke0890e52015-06-17 11:17:08 -07002231 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002232 mAddedHeadsUpChildren.remove(child);
2233 return false;
2234 }
Selim Cinek0fccc722015-07-29 17:04:36 -07002235 if (isClickedHeadsUp(child)) {
2236 // An animation is already running, add it to the Overlay
2237 mClearOverlayViewsWhenFinished.add(child);
2238 return true;
2239 }
Selim Cinekb5605e52015-02-20 18:21:41 +01002240 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002241 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02002242 // Generate Animations
2243 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002244 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002245 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02002246 } else {
2247 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002248 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002249 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02002250 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002251 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002252 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002253 }
2254
Selim Cinek0fccc722015-07-29 17:04:36 -07002255 private boolean isClickedHeadsUp(View child) {
2256 return HeadsUpManager.isClickedHeadsUpNotification(child);
2257 }
2258
Selim Cineke0890e52015-06-17 11:17:08 -07002259 /**
2260 * Remove a removed child view from the heads up animations if it was just added there
2261 *
2262 * @return whether any child was removed from the list to animate
2263 */
2264 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2265 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002266 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2267 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07002268 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002269 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07002270 mTmpList.add(eventPair);
2271 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002272 }
2273 }
Selim Cineke0890e52015-06-17 11:17:08 -07002274 if (hasAddEvent) {
2275 // This child was just added lets remove all events.
2276 mHeadsUpChangeAnimations.removeAll(mTmpList);
2277 }
2278 mTmpList.clear();
2279 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002280 }
2281
Selim Cinek572bbd42014-04-25 16:43:27 +02002282 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01002283 * @param child the child to query
2284 * @return whether a view is not a top level child but a child notification and that group is
2285 * not expanded
2286 */
2287 private boolean isChildInInvisibleGroup(View child) {
2288 if (child instanceof ExpandableNotificationRow) {
2289 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2290 ExpandableNotificationRow groupSummary =
2291 mGroupManager.getGroupSummary(row.getStatusBarNotification());
2292 if (groupSummary != null && groupSummary != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07002293 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01002294 }
2295 }
2296 return false;
2297 }
2298
2299 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02002300 * Updates the scroll position when a child was removed
2301 *
2302 * @param removedChild the removed child
2303 */
Selim Cinek61633a82016-01-25 15:54:10 -08002304 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002305 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinek42357e02016-02-24 18:48:01 -08002306 int padding = (int) NotificationUtils.interpolate(
2307 mPaddingBetweenElements,
2308 mIncreasedPaddingBetweenElements,
2309 removedChild.getIncreasedPaddingAmount());
Selim Cinek61633a82016-01-25 15:54:10 -08002310 int childHeight = getIntrinsicHeight(removedChild) + padding;
Selim Cinek572bbd42014-04-25 16:43:27 +02002311 int endPosition = startingPosition + childHeight;
2312 if (endPosition <= mOwnScrollY) {
2313 // This child is fully scrolled of the top, so we have to deduct its height from the
2314 // scrollPosition
2315 mOwnScrollY -= childHeight;
2316 } else if (startingPosition < mOwnScrollY) {
2317 // This child is currently being scrolled into, set the scroll position to the start of
2318 // this child
2319 mOwnScrollY = startingPosition;
2320 }
2321 }
2322
Selim Cinekd7c4e002014-07-04 18:36:42 +02002323 private int getIntrinsicHeight(View view) {
2324 if (view instanceof ExpandableView) {
2325 ExpandableView expandableView = (ExpandableView) view;
2326 return expandableView.getIntrinsicHeight();
2327 }
2328 return view.getHeight();
2329 }
2330
Selim Cinek572bbd42014-04-25 16:43:27 +02002331 private int getPositionInLinearLayout(View requestedChild) {
2332 int position = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08002333 float previousIncreasedAmount = 0.0f;
Selim Cinek572bbd42014-04-25 16:43:27 +02002334 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002335 ExpandableView child = (ExpandableView) getChildAt(i);
2336 boolean notGone = child.getVisibility() != View.GONE;
2337 if (notGone) {
Selim Cinek42357e02016-02-24 18:48:01 -08002338 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
Selim Cinek61633a82016-01-25 15:54:10 -08002339 if (position != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08002340 position += (int) NotificationUtils.interpolate(
2341 mPaddingBetweenElements,
2342 mIncreasedPaddingBetweenElements,
2343 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Selim Cinek61633a82016-01-25 15:54:10 -08002344 }
Selim Cinek42357e02016-02-24 18:48:01 -08002345 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08002346 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002347 if (child == requestedChild) {
2348 return position;
2349 }
Selim Cinek61633a82016-01-25 15:54:10 -08002350 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02002351 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002352 }
2353 }
2354 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02002355 }
2356
2357 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002358 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02002359 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002360 onViewAddedInternal(child);
2361 }
2362
Selim Cinek614576e2016-01-20 10:54:09 -08002363 private void updateFirstAndLastBackgroundViews() {
2364 ActivatableNotificationView firstChild = getFirstChildWithBackground();
2365 ActivatableNotificationView lastChild = getLastChildWithBackground();
2366 if (mAnimationsEnabled && mIsExpanded) {
2367 mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
2368 mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
2369 } else {
2370 mAnimateNextBackgroundTop = false;
2371 mAnimateNextBackgroundBottom = false;
2372 }
2373 mFirstVisibleBackgroundChild = firstChild;
2374 mLastVisibleBackgroundChild = lastChild;
2375 }
2376
Selim Cinekb5605e52015-02-20 18:21:41 +01002377 private void onViewAddedInternal(View child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002378 updateHideSensitiveForChild(child);
Jorim Jaggibe565df2014-04-28 17:51:23 +02002379 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00002380 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002381 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02002382 updateChronometerForChild(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002383 }
2384
Selim Cinekd06c41c2015-07-06 14:51:36 -07002385 private void updateHideSensitiveForChild(View child) {
Selim Cinekcfcd23e2016-05-05 12:31:42 -04002386 if (child instanceof ExpandableView) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002387 ExpandableView expandableView = (ExpandableView) child;
Selim Cinekcfcd23e2016-05-05 12:31:42 -04002388 expandableView.setHideSensitiveForIntrinsicHeight(mAmbientState.isHideSensitive());
Selim Cinekd06c41c2015-07-06 14:51:36 -07002389 }
2390 }
2391
Selim Cinekd1395642016-04-28 12:22:42 -07002392 public void notifyGroupChildRemoved(View row, ViewGroup childrenContainer) {
2393 onViewRemovedInternal(row, childrenContainer);
Selim Cinekb5605e52015-02-20 18:21:41 +01002394 }
2395
2396 public void notifyGroupChildAdded(View row) {
2397 onViewAddedInternal(row);
2398 }
2399
Jorim Jaggi75c95042014-05-16 19:09:59 +02002400 public void setAnimationsEnabled(boolean animationsEnabled) {
2401 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002402 updateNotificationAnimationStates();
2403 }
2404
2405 private void updateNotificationAnimationStates() {
Selim Cinekcd5b22f2016-03-08 16:15:41 -08002406 boolean running = mAnimationsEnabled || mPulsing;
Selim Cinekcab4a602014-09-03 14:47:57 +02002407 int childCount = getChildCount();
2408 for (int i = 0; i < childCount; i++) {
2409 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07002410 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02002411 updateAnimationState(running, child);
2412 }
2413 }
2414
Selim Cinek51ae05d2014-09-09 15:51:38 +02002415 private void updateAnimationState(View child) {
Selim Cinekcd5b22f2016-03-08 16:15:41 -08002416 updateAnimationState((mAnimationsEnabled || mPulsing)
2417 && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002418 }
2419
2420
Selim Cinekcab4a602014-09-03 14:47:57 +02002421 private void updateAnimationState(boolean running, View child) {
2422 if (child instanceof ExpandableNotificationRow) {
2423 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2424 row.setIconAnimationRunning(running);
2425 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02002426 }
2427
2428 public boolean isAddOrRemoveAnimationPending() {
2429 return mNeedsAnimation
2430 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
2431 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002432 /**
2433 * Generate an animation for an added child view.
2434 *
2435 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00002436 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002437 */
Jorim Jaggif6411742014-08-05 17:10:43 +00002438 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002439 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002440 // Generate Animations
2441 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00002442 if (fromMoreCard) {
2443 mFromMoreCardAdditions.add(child);
2444 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002445 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02002446 }
Adrian Roos777ef562015-12-01 17:37:14 -08002447 if (isHeadsUp(child) && !mChangePositionInProgress) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002448 mAddedHeadsUpChildren.add(child);
2449 mChildrenToAddAnimated.remove(child);
2450 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002451 }
2452
2453 /**
2454 * Change the position of child to a new location
2455 *
2456 * @param child the view to change the position for
2457 * @param newIndex the new index
2458 */
2459 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002460 int currentIndex = indexOfChild(child);
2461 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002462 mChangePositionInProgress = true;
Adrian Roos14503e22016-03-09 14:01:24 -08002463 ((ExpandableView)child).setChangingPosition(true);
Selim Cinekc27437b2014-05-14 10:23:33 +02002464 removeView(child);
2465 addView(child, newIndex);
Adrian Roos14503e22016-03-09 14:01:24 -08002466 ((ExpandableView)child).setChangingPosition(false);
Selim Cinek159ffdb2014-06-04 22:24:18 +02002467 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04002468 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002469 mChildrenChangingPositions.add(child);
2470 mNeedsAnimation = true;
2471 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002472 }
2473 }
2474
Selim Cinekf4c19962014-05-01 21:55:31 +02002475 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002476 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002477 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002478 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002479 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002480 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002481 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
2482 mGoToFullShadeDelay);
Selim Cinek614576e2016-01-20 10:54:09 -08002483 setAnimationRunning(true);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002484 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08002485 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08002486 updateViewShadows();
Selim Cinekf4c19962014-05-01 21:55:31 +02002487 } else {
2488 applyCurrentState();
2489 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002490 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02002491 }
2492
2493 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07002494 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02002495 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002496 generateChildAdditionEvents();
2497 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02002498 generateSnapBackEvents();
2499 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002500 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002501 generateActivateEvent();
2502 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02002503 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04002504 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002505 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02002506 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01002507 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02002508 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002509 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002510 }
2511
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002512 private void generateHeadsUpAnimationEvents() {
2513 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002514 ExpandableNotificationRow row = eventPair.first;
2515 boolean isHeadsUp = eventPair.second;
2516 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
2517 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07002518 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07002519 if (!mIsExpanded && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07002520 type = row.wasJustClicked()
2521 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
2522 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinekeaee9c02015-06-25 11:04:20 -04002523 } else {
2524 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
2525 if (viewState == null) {
2526 // A view state was never generated for this view, so we don't need to animate
2527 // this. This may happen with notification children.
2528 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07002529 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04002530 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
2531 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
2532 // Our custom add animation
2533 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
2534 } else {
2535 // Normal add animation
2536 type = AnimationEvent.ANIMATION_TYPE_ADD;
2537 }
2538 onBottom = !pinnedAndClosed;
2539 }
Selim Cineka59ecc32015-04-07 10:51:49 -07002540 }
2541 AnimationEvent event = new AnimationEvent(row, type);
2542 event.headsUpFromBottom = onBottom;
2543 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002544 }
2545 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07002546 mAddedHeadsUpChildren.clear();
2547 }
2548
Selim Cinekeaee9c02015-06-25 11:04:20 -04002549 private boolean shouldHunAppearFromBottom(StackViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002550 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
2551 return false;
2552 }
2553 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002554 }
2555
Selim Cinekb5605e52015-02-20 18:21:41 +01002556 private void generateGroupExpansionEvent() {
2557 // Generate a group expansion/collapsing event if there is such a group at all
2558 if (mExpandedGroupView != null) {
2559 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
2560 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
2561 mExpandedGroupView = null;
2562 }
2563 }
2564
Selim Cineka5e211b2014-08-11 17:35:48 +02002565 private void generateViewResizeEvent() {
2566 if (mNeedViewResizeAnimation) {
2567 mAnimationEvents.add(
2568 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
2569 }
2570 mNeedViewResizeAnimation = false;
2571 }
2572
Selim Cinekeb973562014-05-02 17:07:49 +02002573 private void generateSnapBackEvents() {
2574 for (View child : mSnappedBackChildren) {
2575 mAnimationEvents.add(new AnimationEvent(child,
2576 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
2577 }
2578 mSnappedBackChildren.clear();
2579 }
2580
2581 private void generateDragEvents() {
2582 for (View child : mDragAnimPendingChildren) {
2583 mAnimationEvents.add(new AnimationEvent(child,
2584 AnimationEvent.ANIMATION_TYPE_START_DRAG));
2585 }
2586 mDragAnimPendingChildren.clear();
2587 }
2588
Selim Cinek572bbd42014-04-25 16:43:27 +02002589 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002590 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002591 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
2592 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002593 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
2594 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002595 AnimationEvent event = new AnimationEvent(child, animationType);
2596
2597 // we need to know the view after this one
2598 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
2599 mAnimationEvents.add(event);
Selim Cinekd1395642016-04-28 12:22:42 -07002600 mSwipedOutViews.remove(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002601 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002602 mChildrenToRemoveAnimated.clear();
2603 }
2604
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002605 private void generatePositionChangeEvents() {
2606 for (View child : mChildrenChangingPositions) {
2607 mAnimationEvents.add(new AnimationEvent(child,
2608 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2609 }
2610 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01002611 if (mGenerateChildOrderChangedEvent) {
2612 mAnimationEvents.add(new AnimationEvent(null,
2613 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2614 mGenerateChildOrderChangedEvent = false;
2615 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002616 }
2617
Selim Cinek572bbd42014-04-25 16:43:27 +02002618 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002619 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002620 if (mFromMoreCardAdditions.contains(child)) {
2621 mAnimationEvents.add(new AnimationEvent(child,
2622 AnimationEvent.ANIMATION_TYPE_ADD,
2623 StackStateAnimator.ANIMATION_DURATION_STANDARD));
2624 } else {
2625 mAnimationEvents.add(new AnimationEvent(child,
2626 AnimationEvent.ANIMATION_TYPE_ADD));
2627 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002628 }
2629 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002630 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04002631 }
2632
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002633 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02002634 if (mTopPaddingNeedsAnimation) {
2635 mAnimationEvents.add(
2636 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2637 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002638 mTopPaddingNeedsAnimation = false;
2639 }
2640
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002641 private void generateActivateEvent() {
2642 if (mActivateNeedsAnimation) {
2643 mAnimationEvents.add(
2644 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2645 }
2646 mActivateNeedsAnimation = false;
2647 }
2648
Selim Cinekd9acca52014-09-01 22:33:25 +02002649 private void generateAnimateEverythingEvent() {
2650 if (mEverythingNeedsAnimation) {
2651 mAnimationEvents.add(
2652 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2653 }
2654 mEverythingNeedsAnimation = false;
2655 }
2656
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002657 private void generateDimmedEvent() {
2658 if (mDimmedNeedsAnimation) {
2659 mAnimationEvents.add(
2660 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2661 }
2662 mDimmedNeedsAnimation = false;
2663 }
2664
Jorim Jaggiae441282014-08-01 02:45:18 +02002665 private void generateHideSensitiveEvent() {
2666 if (mHideSensitiveNeedsAnimation) {
2667 mAnimationEvents.add(
2668 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2669 }
2670 mHideSensitiveNeedsAnimation = false;
2671 }
2672
John Spurlockbf370992014-06-17 13:58:31 -04002673 private void generateDarkEvent() {
2674 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002675 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2676 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2677 mAnimationEvents.add(ev);
Selim Cinek972123d2016-05-03 14:25:58 -07002678 startBackgroundFadeIn();
John Spurlockbf370992014-06-17 13:58:31 -04002679 }
2680 mDarkNeedsAnimation = false;
2681 }
2682
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002683 private void generateGoToFullShadeEvent() {
2684 if (mGoToFullShadeNeedsAnimation) {
2685 mAnimationEvents.add(
2686 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2687 }
2688 mGoToFullShadeNeedsAnimation = false;
2689 }
2690
Selim Cinek67b22602014-03-10 15:40:16 +01002691 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002692 if (!isScrollingEnabled()) {
2693 return false;
2694 }
Selim Cinek67b22602014-03-10 15:40:16 +01002695 /*
2696 * This method JUST determines whether we want to intercept the motion.
2697 * If we return true, onMotionEvent will be called and we do the actual
2698 * scrolling there.
2699 */
2700
2701 /*
2702 * Shortcut the most recurring case: the user is in the dragging
Chris Wren5d53df42015-06-26 11:26:03 -04002703 * state and is moving their finger. We want to intercept this
Selim Cinek67b22602014-03-10 15:40:16 +01002704 * motion.
2705 */
2706 final int action = ev.getAction();
2707 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2708 return true;
2709 }
2710
Selim Cinek67b22602014-03-10 15:40:16 +01002711 switch (action & MotionEvent.ACTION_MASK) {
2712 case MotionEvent.ACTION_MOVE: {
2713 /*
2714 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04002715 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01002716 */
2717
2718 /*
2719 * Locally do absolute value. mLastMotionY is set to the y value
2720 * of the down event.
2721 */
2722 final int activePointerId = mActivePointerId;
2723 if (activePointerId == INVALID_POINTER) {
2724 // If we don't have a valid id, the touch down wasn't on content.
2725 break;
2726 }
2727
2728 final int pointerIndex = ev.findPointerIndex(activePointerId);
2729 if (pointerIndex == -1) {
2730 Log.e(TAG, "Invalid pointerId=" + activePointerId
2731 + " in onInterceptTouchEvent");
2732 break;
2733 }
2734
2735 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02002736 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01002737 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02002738 final int xDiff = Math.abs(x - mDownX);
2739 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01002740 setIsBeingDragged(true);
2741 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002742 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01002743 initVelocityTrackerIfNotExists();
2744 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01002745 }
2746 break;
2747 }
2748
2749 case MotionEvent.ACTION_DOWN: {
2750 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05302751 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01002752 if (getChildAtPosition(ev.getX(), y) == null) {
2753 setIsBeingDragged(false);
2754 recycleVelocityTracker();
2755 break;
2756 }
2757
2758 /*
2759 * Remember location of down touch.
2760 * ACTION_DOWN always refers to pointer index 0.
2761 */
2762 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002763 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01002764 mActivePointerId = ev.getPointerId(0);
2765
2766 initOrResetVelocityTracker();
2767 mVelocityTracker.addMovement(ev);
2768 /*
2769 * If being flinged and user touches the screen, initiate drag;
2770 * otherwise don't. mScroller.isFinished should be false when
2771 * being flinged.
2772 */
2773 boolean isBeingDragged = !mScroller.isFinished();
2774 setIsBeingDragged(isBeingDragged);
2775 break;
2776 }
2777
2778 case MotionEvent.ACTION_CANCEL:
2779 case MotionEvent.ACTION_UP:
2780 /* Release the drag */
2781 setIsBeingDragged(false);
2782 mActivePointerId = INVALID_POINTER;
2783 recycleVelocityTracker();
2784 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2785 postInvalidateOnAnimation();
2786 }
2787 break;
2788 case MotionEvent.ACTION_POINTER_UP:
2789 onSecondaryPointerUp(ev);
2790 break;
2791 }
2792
2793 /*
2794 * The only time we want to intercept motion events is if we are in the
2795 * drag mode.
2796 */
2797 return mIsBeingDragged;
2798 }
2799
Muyuan Li333a4fc2016-04-16 17:13:46 -07002800 protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
2801 return new StackScrollAlgorithm(context);
2802 }
2803
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002804 /**
2805 * @return Whether the specified motion event is actually happening over the content.
2806 */
2807 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01002808 return isInContentBounds(event.getY());
2809 }
2810
2811 /**
2812 * @return Whether a y coordinate is inside the content.
2813 */
2814 public boolean isInContentBounds(float y) {
2815 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002816 }
2817
Selim Cinek67b22602014-03-10 15:40:16 +01002818 private void setIsBeingDragged(boolean isDragged) {
2819 mIsBeingDragged = isDragged;
2820 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002821 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02002822 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002823 }
2824 }
2825
2826 @Override
2827 public void onWindowFocusChanged(boolean hasWindowFocus) {
2828 super.onWindowFocusChanged(hasWindowFocus);
2829 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002830 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002831 }
2832 }
Selim Cinekfab078b2014-03-27 22:45:58 +01002833
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07002834 @Override
Adrian Roos181385c2016-05-05 17:45:44 -04002835 public void clearChildFocus(View child) {
2836 super.clearChildFocus(child);
2837 if (mForcedScroll == child) {
2838 mForcedScroll = null;
2839 }
2840 }
2841
2842 @Override
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07002843 public void requestDisallowLongPress() {
2844 removeLongPressCallback();
2845 }
2846
Adrian Roosfa139752016-04-27 09:59:08 -07002847 @Override
2848 public void requestDisallowDismiss() {
2849 mDisallowDismissInThisMotion = true;
2850 }
2851
Selim Cinek1408eb52014-06-02 14:45:38 +02002852 public void removeLongPressCallback() {
2853 mSwipeHelper.removeLongPressCallback();
2854 }
2855
Selim Cinekfab078b2014-03-27 22:45:58 +01002856 @Override
2857 public boolean isScrolledToTop() {
2858 return mOwnScrollY == 0;
2859 }
2860
2861 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002862 public boolean isScrolledToBottom() {
2863 return mOwnScrollY >= getScrollRange();
2864 }
2865
2866 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01002867 public View getHostView() {
2868 return this;
2869 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002870
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002871 public int getEmptyBottomMargin() {
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002872 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize
2873 - mBottomStackSlowDownHeight;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002874 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002875 }
2876
Selim Cinek5f71bee2015-11-18 10:25:23 -08002877 public float getKeyguardBottomStackSize() {
2878 return mBottomStackPeekSize + getResources().getDimensionPixelSize(
2879 R.dimen.bottom_stack_slow_down_length);
2880 }
2881
Selim Cinek1685e632014-04-08 02:27:49 +02002882 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002883 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02002884 }
2885
2886 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002887 mIsExpansionChanging = false;
Selim Cinek4fe3e472014-07-03 16:32:54 +02002888 if (!mIsExpanded) {
2889 mOwnScrollY = 0;
Selim Cinek78f40082016-03-10 18:06:27 -08002890 mPhoneStatusBar.resetUserExpandedStates();
Selim Cinekf336f4c2014-11-12 16:58:16 +01002891
Selim Cinekd1395642016-04-28 12:22:42 -07002892 // lets make sure nothing is in the overlay / transient anymore
Selim Cineka5703182016-05-11 21:23:16 -04002893 clearTemporaryViews(this);
Selim Cinekd1395642016-04-28 12:22:42 -07002894 for (int i = 0; i < getChildCount(); i++) {
2895 ExpandableView child = (ExpandableView) getChildAt(i);
2896 if (child instanceof ExpandableNotificationRow) {
2897 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
Selim Cineka5703182016-05-11 21:23:16 -04002898 clearTemporaryViews(row.getChildrenContainer());
Selim Cinekd1395642016-04-28 12:22:42 -07002899 }
2900 }
Selim Cinek4fe3e472014-07-03 16:32:54 +02002901 }
Selim Cinek1685e632014-04-08 02:27:49 +02002902 }
2903
Selim Cineka5703182016-05-11 21:23:16 -04002904 private void clearTemporaryViews(ViewGroup viewGroup) {
Selim Cinekd1395642016-04-28 12:22:42 -07002905 while (viewGroup != null && viewGroup.getTransientViewCount() != 0) {
Selim Cinek81f26d32016-05-09 18:54:10 -04002906 viewGroup.removeTransientView(viewGroup.getTransientView(0));
Selim Cinekd1395642016-04-28 12:22:42 -07002907 }
Selim Cineka5703182016-05-11 21:23:16 -04002908 if (viewGroup != null) {
2909 viewGroup.getOverlay().clear();
2910 }
Selim Cinekd1395642016-04-28 12:22:42 -07002911 }
2912
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002913 public void onPanelTrackingStarted() {
2914 mPanelTracking = true;
2915 }
2916 public void onPanelTrackingStopped() {
2917 mPanelTracking = false;
2918 }
2919
Selim Cinekb24e0a92015-06-09 20:17:30 -07002920 public void resetScrollPosition() {
2921 mScroller.abortAnimation();
2922 mOwnScrollY = 0;
2923 }
2924
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02002925 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02002926 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02002927 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02002928 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002929 if (changed) {
Selim Cinek9184f9c2016-02-02 17:36:53 -08002930 if (!mIsExpanded) {
2931 mGroupManager.collapseAllGroups();
Dan Sandlercb012892016-03-01 00:45:06 -05002932 } else {
2933 // XXX: HACK: we should not be clearing notification effects from way down here.
2934 // But at the moment we don't have a reliable way to know when the window is
2935 // actually exposed to the air, so
2936 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
2937 if (DEBUG) {
2938 Log.v(TAG, "clearing notification effects from scroller");
2939 }
2940 mPhoneStatusBar.clearNotificationEffects();
2941 }
Selim Cinek9184f9c2016-02-02 17:36:53 -08002942 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002943 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02002944 updateChronometers();
2945 }
2946 }
2947
2948 private void updateChronometers() {
2949 int childCount = getChildCount();
2950 for (int i = 0; i < childCount; i++) {
2951 updateChronometerForChild(getChildAt(i));
2952 }
2953 }
2954
2955 private void updateChronometerForChild(View child) {
2956 if (child instanceof ExpandableNotificationRow) {
2957 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2958 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002959 }
Selim Cinek1685e632014-04-08 02:27:49 +02002960 }
2961
Jorim Jaggibe565df2014-04-28 17:51:23 +02002962 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01002963 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002964 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002965 updateScrollPositionOnExpandInBottom(view);
2966 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02002967 notifyHeightChangeListener(view);
Selim Cinekb5605e52015-02-20 18:21:41 +01002968 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08002969 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
2970 ? (ExpandableNotificationRow) view
2971 : null;
2972 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01002973 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002974 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02002975 }
2976
Selim Cineka5e211b2014-08-11 17:35:48 +02002977 @Override
2978 public void onReset(ExpandableView view) {
Selim Cinek0e41dea2014-08-25 13:55:06 +02002979 if (mIsExpanded && mAnimationsEnabled) {
2980 mRequestViewResizeAnimationOnLayout = true;
2981 }
Selim Cinek51ae05d2014-09-09 15:51:38 +02002982 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02002983 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02002984 }
2985
Selim Cinekf7a14c02014-07-07 14:01:46 +02002986 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2987 if (view instanceof ExpandableNotificationRow) {
2988 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002989 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08002990 if (row.isSummaryWithChildren()) {
2991 return;
2992 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02002993 // We are actually expanding this view
Selim Cinek4e4cac32016-03-11 16:45:52 -08002994 float endPosition = row.getTranslationY() + row.getActualHeight();
Selim Cinek388df6d2015-10-22 13:25:11 -07002995 if (row.isChildInGroup()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08002996 endPosition += row.getNotificationParent().getTranslationY();
Selim Cinek388df6d2015-10-22 13:25:11 -07002997 }
Selim Cinek1b2a05e2016-04-28 14:20:39 -07002998 int stackEnd = getStackEndPosition();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002999 if (endPosition > stackEnd) {
3000 mOwnScrollY += endPosition - stackEnd;
3001 mDisallowScrollingInThisMotion = true;
3002 }
3003 }
3004 }
3005 }
3006
Selim Cinek1b2a05e2016-04-28 14:20:39 -07003007 private int getStackEndPosition() {
3008 return mMaxLayoutHeight - mBottomStackPeekSize - mBottomStackSlowDownHeight
3009 + mPaddingBetweenElements + (int) mStackTranslation;
3010 }
3011
Jorim Jaggibe565df2014-04-28 17:51:23 +02003012 public void setOnHeightChangedListener(
3013 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
3014 this.mOnHeightChangedListener = mOnHeightChangedListener;
3015 }
3016
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003017 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
3018 mOnEmptySpaceClickListener = listener;
3019 }
3020
Selim Cinek572bbd42014-04-25 16:43:27 +02003021 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08003022 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02003023 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07003024 runAnimationFinishedRunnables();
Selim Cinek0fccc722015-07-29 17:04:36 -07003025 clearViewOverlays();
3026 }
3027
3028 private void clearViewOverlays() {
3029 for (View view : mClearOverlayViewsWhenFinished) {
Selim Cineka5703182016-05-11 21:23:16 -04003030 StackStateAnimator.removeFromOverlay(view);
Selim Cinek0fccc722015-07-29 17:04:36 -07003031 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07003032 }
3033
3034 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003035 for (Runnable runnable : mAnimationFinishedRunnables) {
3036 runnable.run();
3037 }
3038 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02003039 }
3040
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003041 /**
3042 * See {@link AmbientState#setDimmed}.
3043 */
3044 public void setDimmed(boolean dimmed, boolean animate) {
3045 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02003046 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003047 mDimmedNeedsAnimation = true;
3048 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08003049 animateDimmed(dimmed);
3050 } else {
3051 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003052 }
3053 requestChildrenUpdate();
3054 }
3055
Selim Cinekd35c2792016-01-21 13:20:57 -08003056 private void setDimAmount(float dimAmount) {
3057 mDimAmount = dimAmount;
3058 updateBackgroundDimming();
3059 }
3060
3061 private void animateDimmed(boolean dimmed) {
3062 if (mDimAnimator != null) {
3063 mDimAnimator.cancel();
3064 }
3065 float target = dimmed ? 1.0f : 0.0f;
3066 if (target == mDimAmount) {
3067 return;
3068 }
3069 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
3070 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
3071 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
3072 mDimAnimator.addListener(mDimEndListener);
3073 mDimAnimator.addUpdateListener(mDimUpdateListener);
3074 mDimAnimator.start();
3075 }
3076
Jorim Jaggiae441282014-08-01 02:45:18 +02003077 public void setHideSensitive(boolean hideSensitive, boolean animate) {
3078 if (hideSensitive != mAmbientState.isHideSensitive()) {
3079 int childCount = getChildCount();
3080 for (int i = 0; i < childCount; i++) {
3081 ExpandableView v = (ExpandableView) getChildAt(i);
3082 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
3083 }
3084 mAmbientState.setHideSensitive(hideSensitive);
3085 if (animate && mAnimationsEnabled) {
3086 mHideSensitiveNeedsAnimation = true;
3087 mNeedsAnimation = true;
3088 }
3089 requestChildrenUpdate();
3090 }
3091 }
3092
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003093 /**
3094 * See {@link AmbientState#setActivatedChild}.
3095 */
Selim Cineka32ab602014-06-11 15:06:01 +02003096 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003097 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02003098 if (mAnimationsEnabled) {
3099 mActivateNeedsAnimation = true;
3100 mNeedsAnimation = true;
3101 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003102 requestChildrenUpdate();
3103 }
3104
Selim Cineka32ab602014-06-11 15:06:01 +02003105 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003106 return mAmbientState.getActivatedChild();
3107 }
3108
Selim Cinek572bbd42014-04-25 16:43:27 +02003109 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02003110 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02003111 if (mListener != null) {
3112 mListener.onChildLocationsChanged(this);
3113 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07003114 runAnimationFinishedRunnables();
Selim Cinek6811d722016-01-19 17:53:12 -08003115 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08003116 updateViewShadows();
3117 }
3118
3119 private void updateViewShadows() {
3120 // we need to work around an issue where the shadow would not cast between siblings when
3121 // their z difference is between 0 and 0.1
3122
3123 // Lefts first sort by Z difference
3124 for (int i = 0; i < getChildCount(); i++) {
3125 ExpandableView child = (ExpandableView) getChildAt(i);
3126 if (child.getVisibility() != GONE) {
3127 mTmpSortedChildren.add(child);
3128 }
3129 }
3130 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
3131
3132 // Now lets update the shadow for the views
3133 ExpandableView previous = null;
3134 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
3135 ExpandableView expandableView = mTmpSortedChildren.get(i);
3136 float translationZ = expandableView.getTranslationZ();
3137 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
3138 float diff = otherZ - translationZ;
3139 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
3140 // There is no fake shadow to be drawn
3141 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
3142 } else {
3143 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
3144 expandableView.getTranslationY();
3145 expandableView.setFakeShadowIntensity(diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
3146 previous.getOutlineAlpha(), (int) yLocation,
3147 previous.getOutlineTranslation());
3148 }
3149 previous = expandableView;
3150 }
3151
3152 mTmpSortedChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02003153 }
3154
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003155 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003156 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003157 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003158 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003159 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003160 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003161 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02003162 }
3163
Selim Cinek1408eb52014-06-02 14:45:38 +02003164 public void cancelExpandHelper() {
3165 mExpandHelper.cancel();
3166 }
3167
3168 public void setIntrinsicPadding(int intrinsicPadding) {
3169 mIntrinsicPadding = intrinsicPadding;
3170 }
3171
Jorim Jaggi30c305c2014-07-01 23:34:41 +02003172 public int getIntrinsicPadding() {
3173 return mIntrinsicPadding;
3174 }
3175
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003176 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02003177 * @return the y position of the first notification
3178 */
3179 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07003180 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02003181 }
3182
Selim Cinekc0ce82d2014-06-10 13:21:15 +02003183 @Override
3184 public boolean shouldDelayChildPressedState() {
3185 return true;
3186 }
3187
Jorim Jaggi457cc352014-06-02 22:47:42 +02003188 /**
John Spurlockbf370992014-06-17 13:58:31 -04003189 * See {@link AmbientState#setDark}.
3190 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003191 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04003192 mAmbientState.setDark(dark);
3193 if (animate && mAnimationsEnabled) {
3194 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003195 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04003196 mNeedsAnimation = true;
Selim Cinek972123d2016-05-03 14:25:58 -07003197 setBackgroundFadeAmount(0.0f);
3198 } else if (!dark) {
3199 setBackgroundFadeAmount(1.0f);
John Spurlockbf370992014-06-17 13:58:31 -04003200 }
3201 requestChildrenUpdate();
Selim Cinek6811d722016-01-19 17:53:12 -08003202 if (dark) {
3203 setWillNotDraw(!DEBUG);
3204 mScrimController.setExcludedBackgroundArea(null);
3205 } else {
3206 updateBackground();
3207 setWillNotDraw(false);
Selim Cinek6811d722016-01-19 17:53:12 -08003208 }
John Spurlockbf370992014-06-17 13:58:31 -04003209 }
3210
Selim Cinek972123d2016-05-03 14:25:58 -07003211 private void setBackgroundFadeAmount(float fadeAmount) {
3212 mBackgroundFadeAmount = fadeAmount;
3213 updateBackgroundDimming();
3214 }
3215
3216 public float getBackgroundFadeAmount() {
3217 return mBackgroundFadeAmount;
3218 }
3219
3220 private void startBackgroundFadeIn() {
3221 ObjectAnimator fadeAnimator = ObjectAnimator.ofFloat(this, BACKGROUND_FADE, 0f, 1f);
3222 int maxLength;
3223 if (mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE
3224 || mDarkAnimationOriginIndex == AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW) {
3225 maxLength = getNotGoneChildCount() - 1;
3226 } else {
3227 maxLength = Math.max(mDarkAnimationOriginIndex,
3228 getNotGoneChildCount() - mDarkAnimationOriginIndex - 1);
3229 }
3230 long delay = maxLength * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_DARK;
3231 fadeAnimator.setStartDelay(delay);
3232 fadeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
3233 fadeAnimator.setInterpolator(Interpolators.ALPHA_IN);
3234 fadeAnimator.start();
3235 }
3236
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003237 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
3238 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
3239 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
3240 }
3241 if (screenLocation.y > getBottomMostNotificationBottom()) {
3242 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
3243 }
3244 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
3245 if (child != null) {
3246 return getNotGoneIndex(child);
3247 } else {
3248 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
3249 }
3250 }
3251
3252 private int getNotGoneIndex(View child) {
3253 int count = getChildCount();
3254 int notGoneIndex = 0;
3255 for (int i = 0; i < count; i++) {
3256 View v = getChildAt(i);
3257 if (child == v) {
3258 return notGoneIndex;
3259 }
3260 if (v.getVisibility() != View.GONE) {
3261 notGoneIndex++;
3262 }
3263 }
3264 return -1;
3265 }
3266
Dan Sandlereceda3d2014-07-21 15:35:01 -04003267 public void setDismissView(DismissView dismissView) {
Selim Cinek01af3342016-02-09 19:25:31 -08003268 int index = -1;
3269 if (mDismissView != null) {
3270 index = indexOfChild(mDismissView);
3271 removeView(mDismissView);
3272 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003273 mDismissView = dismissView;
Selim Cinek01af3342016-02-09 19:25:31 -08003274 addView(mDismissView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003275 }
3276
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003277 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08003278 int index = -1;
3279 if (mEmptyShadeView != null) {
3280 index = indexOfChild(mEmptyShadeView);
3281 removeView(mEmptyShadeView);
3282 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003283 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08003284 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003285 }
3286
3287 public void updateEmptyShadeView(boolean visible) {
3288 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
3289 int newVisibility = visible ? VISIBLE : GONE;
3290 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003291 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003292 if (mEmptyShadeView.willBeGone()) {
3293 mEmptyShadeView.cancelAnimation();
3294 } else {
3295 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003296 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003297 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003298 mEmptyShadeView.setWillBeGone(false);
3299 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003300 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003301 } else {
Selim Cinek20867102014-12-10 17:09:17 +01003302 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003303 @Override
3304 public void run() {
3305 mEmptyShadeView.setVisibility(GONE);
3306 mEmptyShadeView.setWillBeGone(false);
3307 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003308 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003309 }
Selim Cinek20867102014-12-10 17:09:17 +01003310 };
3311 if (mAnimationsEnabled) {
3312 mEmptyShadeView.setWillBeGone(true);
3313 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
3314 } else {
3315 mEmptyShadeView.setInvisible();
3316 onFinishedRunnable.run();
3317 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003318 }
3319 }
3320 }
3321
Selim Cinek2cd45df2015-06-09 18:00:07 -07003322 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
Selim Cinek3e7592d2016-04-11 09:35:54 +08003323 int index = -1;
3324 if (mOverflowContainer != null) {
3325 index = indexOfChild(mOverflowContainer);
3326 removeView(mOverflowContainer);
3327 }
Selim Cinek2cd45df2015-06-09 18:00:07 -07003328 mOverflowContainer = overFlowContainer;
Selim Cinek3e7592d2016-04-11 09:35:54 +08003329 addView(mOverflowContainer, index);
Selim Cinek2cd45df2015-06-09 18:00:07 -07003330 }
3331
3332 public void updateOverflowContainerVisibility(boolean visible) {
3333 int oldVisibility = mOverflowContainer.willBeGone() ? GONE
3334 : mOverflowContainer.getVisibility();
3335 final int newVisibility = visible ? VISIBLE : GONE;
3336 if (oldVisibility != newVisibility) {
3337 Runnable onFinishedRunnable = new Runnable() {
3338 @Override
3339 public void run() {
3340 mOverflowContainer.setVisibility(newVisibility);
3341 mOverflowContainer.setWillBeGone(false);
3342 updateContentHeight();
3343 notifyHeightChangeListener(mOverflowContainer);
3344 }
3345 };
3346 if (!mAnimationsEnabled || !mIsExpanded) {
3347 mOverflowContainer.cancelAppearDrawing();
3348 onFinishedRunnable.run();
3349 } else if (newVisibility != GONE) {
3350 mOverflowContainer.performAddAnimation(0,
3351 StackStateAnimator.ANIMATION_DURATION_STANDARD);
3352 mOverflowContainer.setVisibility(newVisibility);
3353 mOverflowContainer.setWillBeGone(false);
3354 updateContentHeight();
3355 notifyHeightChangeListener(mOverflowContainer);
3356 } else {
3357 mOverflowContainer.performRemoveAnimation(
3358 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3359 0.0f,
3360 onFinishedRunnable);
3361 mOverflowContainer.setWillBeGone(true);
3362 }
3363 }
3364 }
3365
Dan Sandlereceda3d2014-07-21 15:35:01 -04003366 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003367 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003368 int newVisibility = visible ? VISIBLE : GONE;
3369 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003370 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003371 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003372 mDismissView.cancelAnimation();
3373 } else {
3374 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003375 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003376 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003377 mDismissView.setWillBeGone(false);
3378 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003379 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003380 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003381 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003382 @Override
3383 public void run() {
3384 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003385 mDismissView.setWillBeGone(false);
3386 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003387 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003388 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01003389 };
Selim Cinek20867102014-12-10 17:09:17 +01003390 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003391 mDismissView.setWillBeGone(true);
3392 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
3393 } else {
3394 dimissHideFinishRunnable.run();
Selim Cinek7d5f3742014-11-07 18:07:49 +01003395 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003396 }
3397 }
3398 }
3399
3400 public void setDismissAllInProgress(boolean dismissAllInProgress) {
3401 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07003402 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cinek9c17b772015-07-07 20:37:09 -07003403 handleDismissAllClipping();
3404 }
3405
3406 private void handleDismissAllClipping() {
3407 final int count = getChildCount();
3408 boolean previousChildWillBeDismissed = false;
3409 for (int i = 0; i < count; i++) {
3410 ExpandableView child = (ExpandableView) getChildAt(i);
3411 if (child.getVisibility() == GONE) {
3412 continue;
3413 }
3414 if (mDismissAllInProgress && previousChildWillBeDismissed) {
3415 child.setMinClipTopAmount(child.getClipTopAmount());
3416 } else {
3417 child.setMinClipTopAmount(0);
3418 }
3419 previousChildWillBeDismissed = canChildBeDismissed(child);
3420 }
Selim Cineka272dfe2015-02-20 18:12:28 +01003421 }
3422
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003423 public boolean isDismissViewNotGone() {
3424 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
3425 }
3426
3427 public boolean isDismissViewVisible() {
3428 return mDismissView.isVisible();
3429 }
3430
3431 public int getDismissViewHeight() {
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08003432 return mDismissView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003433 }
3434
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01003435 public int getEmptyShadeViewHeight() {
3436 return mEmptyShadeView.getHeight();
3437 }
3438
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003439 public float getBottomMostNotificationBottom() {
3440 final int count = getChildCount();
3441 float max = 0;
3442 for (int childIdx = 0; childIdx < count; childIdx++) {
3443 ExpandableView child = (ExpandableView) getChildAt(childIdx);
3444 if (child.getVisibility() == GONE) {
3445 continue;
3446 }
3447 float bottom = child.getTranslationY() + child.getActualHeight();
3448 if (bottom > max) {
3449 max = bottom;
3450 }
3451 }
Selim Cinekd2281152015-04-10 14:37:46 -07003452 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003453 }
3454
Selim Cinek19c8c702014-08-25 22:09:19 +02003455 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
3456 this.mPhoneStatusBar = phoneStatusBar;
3457 }
3458
Selim Cinekb5605e52015-02-20 18:21:41 +01003459 public void setGroupManager(NotificationGroupManager groupManager) {
3460 this.mGroupManager = groupManager;
3461 }
3462
Selim Cinekd9acca52014-09-01 22:33:25 +02003463 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01003464 requestAnimateEverything();
3465 }
3466
3467 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02003468 if (mIsExpanded && mAnimationsEnabled) {
3469 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01003470 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02003471 requestChildrenUpdate();
3472 }
3473 }
3474
Selim Cinek04fb2582015-06-02 19:58:09 +02003475 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01003476 int childCount = getChildCount();
3477 for (int i = childCount - 1; i >= 0; i--) {
3478 ExpandableView child = (ExpandableView) getChildAt(i);
3479 if (child.getVisibility() != View.GONE) {
3480 float childTop = child.getY();
3481 if (childTop > touchY) {
3482 // we are above a notification entirely let's abort
3483 return false;
3484 }
3485 boolean belowChild = touchY > childTop + child.getActualHeight();
3486 if (child == mDismissView) {
3487 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
3488 touchY - childTop)) {
3489 // We clicked on the dismiss button
3490 return false;
3491 }
3492 } else if (child == mEmptyShadeView) {
3493 // We arrived at the empty shade view, for which we accept all clicks
3494 return true;
3495 } else if (!belowChild){
3496 // We are on a child
3497 return false;
3498 }
3499 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003500 }
Selim Cinek04fb2582015-06-02 19:58:09 +02003501 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003502 }
3503
Selim Cinekb5605e52015-02-20 18:21:41 +01003504 @Override
3505 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
Selim Cinek1b2a05e2016-04-28 14:20:39 -07003506 boolean animated = !mGroupExpandedForMeasure && mAnimationsEnabled
3507 && (mIsExpanded || changedRow.isPinned());
Selim Cinekb5605e52015-02-20 18:21:41 +01003508 if (animated) {
3509 mExpandedGroupView = changedRow;
3510 mNeedsAnimation = true;
3511 }
3512 changedRow.setChildrenExpanded(expanded, animated);
Selim Cinek1b2a05e2016-04-28 14:20:39 -07003513 if (!mGroupExpandedForMeasure) {
3514 onHeightChanged(changedRow, false /* needsAnimation */);
3515 }
Selim Cinekb5605e52015-02-20 18:21:41 +01003516 }
3517
3518 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01003519 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
Selim Cinekef5127e2015-12-21 16:55:58 -08003520 mPhoneStatusBar.requestNotificationUpdate();
3521 }
3522
3523 @Override
Selim Cinek2a739342016-03-17 10:28:55 -07003524 public void onGroupsChanged() {
Selim Cinekef5127e2015-12-21 16:55:58 -08003525 mPhoneStatusBar.requestNotificationUpdate();
Selim Cinekb5605e52015-02-20 18:21:41 +01003526 }
3527
3528 public void generateChildOrderChangedEvent() {
3529 if (mIsExpanded && mAnimationsEnabled) {
3530 mGenerateChildOrderChangedEvent = true;
3531 mNeedsAnimation = true;
3532 requestChildrenUpdate();
3533 }
3534 }
3535
Selim Cinek684a4422015-04-15 16:18:39 -07003536 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003537 mAnimationFinishedRunnables.add(runnable);
3538 }
3539
3540 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
3541 mHeadsUpManager = headsUpManager;
3542 mAmbientState.setHeadsUpManager(headsUpManager);
3543 }
3544
3545 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
3546 if (mAnimationsEnabled) {
3547 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
3548 mNeedsAnimation = true;
3549 requestChildrenUpdate();
3550 }
3551 }
3552
3553 public void setShadeExpanded(boolean shadeExpanded) {
3554 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07003555 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003556 }
3557
Selim Cineka59ecc32015-04-07 10:51:49 -07003558 /**
3559 * Set the boundary for the bottom heads up position. The heads up will always be above this
3560 * position.
3561 *
3562 * @param height the height of the screen
3563 * @param bottomBarHeight the height of the bar on the bottom
3564 */
3565 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
3566 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
3567 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003568 requestChildrenUpdate();
3569 }
3570
3571 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
3572 mTrackingHeadsUp = trackingHeadsUp;
3573 }
3574
Selim Cinekaac93252015-04-14 20:04:12 -07003575 public void setScrimController(ScrimController scrimController) {
3576 mScrimController = scrimController;
Selim Cinekd35c2792016-01-21 13:20:57 -08003577 mScrimController.setScrimBehindChangeRunnable(new Runnable() {
3578 @Override
3579 public void run() {
3580 updateBackgroundDimming();
3581 }
3582 });
Selim Cinekaac93252015-04-14 20:04:12 -07003583 }
3584
Selim Cinekbbc580b2015-06-03 14:11:03 +02003585 public void forceNoOverlappingRendering(boolean force) {
3586 mForceNoOverlappingRendering = force;
3587 }
3588
3589 @Override
3590 public boolean hasOverlappingRendering() {
3591 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
3592 }
3593
Selim Cinek6811d722016-01-19 17:53:12 -08003594 public void setAnimationRunning(boolean animationRunning) {
3595 if (animationRunning != mAnimationRunning) {
3596 if (animationRunning) {
3597 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
3598 } else {
3599 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
3600 }
3601 mAnimationRunning = animationRunning;
Selim Cinek33223572016-02-19 19:32:22 -08003602 updateContinuousShadowDrawing();
Selim Cinek6811d722016-01-19 17:53:12 -08003603 }
3604 }
3605
Selim Cinek3776fe02016-02-04 13:32:43 -08003606 public boolean isExpanded() {
3607 return mIsExpanded;
3608 }
3609
Selim Cinekcd5b22f2016-03-08 16:15:41 -08003610 public void setPulsing(boolean pulsing) {
3611 mPulsing = pulsing;
3612 updateNotificationAnimationStates();
3613 }
3614
Selim Cinek31d37b92016-04-26 09:56:42 -07003615 public void setFadedOut(boolean fadingOut) {
3616 if (fadingOut != mFadedOut) {
3617 mFadedOut = fadingOut;
3618 if (fadingOut) {
3619 mScrimController.setExcludedBackgroundArea(null);
3620 } else {
3621 applyCurrentBackgroundBounds();
3622 }
3623 updateSrcDrawing();
3624 }
3625 }
3626
3627 @Override
3628 public void setAlpha(@FloatRange(from = 0.0, to = 1.0) float alpha) {
3629 super.setAlpha(alpha);
3630 setFadedOut(alpha != 1.0f);
3631 }
3632
Selim Cinek3afd00e2014-08-11 22:32:57 +02003633 /**
Selim Cinek7103fd42016-05-09 22:22:33 -04003634 * Remove the a given view from the viewstate. This is currently used when the children are
3635 * kept in the parent artificially to have a nicer animation.
3636 * @param view the view to remove
3637 */
3638 public void removeViewStateForView(View view) {
3639 mCurrentStackScrollState.removeViewStateForView(view);
3640 }
3641
3642 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003643 * A listener that is notified when some child locations might have changed.
3644 */
3645 public interface OnChildLocationsChangedListener {
3646 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
3647 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003648
Jorim Jaggi290600a2014-05-30 17:02:20 +02003649 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003650 * A listener that is notified when the empty space below the notifications is clicked on
3651 */
3652 public interface OnEmptySpaceClickListener {
3653 public void onEmptySpaceClicked(float x, float y);
3654 }
3655
3656 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02003657 * A listener that gets notified when the overscroll at the top has changed.
3658 */
3659 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02003660
3661 /**
3662 * Notifies a listener that the overscroll has changed.
3663 *
3664 * @param amount the amount of overscroll, in pixels
3665 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
3666 * unrubberbanded motion to directly expand overscroll view (e.g expand
3667 * QS)
3668 */
3669 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02003670
3671 /**
3672 * Notify a listener that the scroller wants to escape from the scrolling motion and
3673 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
3674 *
3675 * @param velocity The velocity that the Scroller had when over flinging
3676 * @param open Should the fling open or close the overscroll view.
3677 */
3678 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02003679 }
3680
Mady Mellor4b80b102016-01-22 08:03:58 -08003681 private class NotificationSwipeHelper extends SwipeHelper {
Mady Mellor97c8df42016-03-22 18:09:39 -07003682 private static final long SHOW_GEAR_DELAY = 60;
3683 private static final long COVER_GEAR_DELAY = 4000;
Mady Mellor4b80b102016-01-22 08:03:58 -08003684 private CheckForDrag mCheckForDrag;
Mady Mellor97c8df42016-03-22 18:09:39 -07003685 private Runnable mFalsingCheck;
Mady Mellor4b80b102016-01-22 08:03:58 -08003686 private Handler mHandler;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003687 private boolean mGearSnappedTo;
3688 private boolean mGearSnappedOnLeft;
Mady Mellor4b80b102016-01-22 08:03:58 -08003689
3690 public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
3691 super(swipeDirection, callback, context);
3692 mHandler = new Handler();
Mady Mellor97c8df42016-03-22 18:09:39 -07003693 mFalsingCheck = new Runnable() {
3694 @Override
3695 public void run() {
3696 resetExposedGearView(true /* animate */, true /* force */);
3697 }
3698 };
Mady Mellor4b80b102016-01-22 08:03:58 -08003699 }
3700
3701 @Override
3702 public void onDownUpdate(View currView) {
3703 // Set the active view
3704 mTranslatingParentView = currView;
3705
3706 // Reset check for drag gesture
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003707 cancelCheckForDrag();
3708 if (mCurrIconRow != null) {
3709 mCurrIconRow.setSnapping(false);
3710 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003711 mCheckForDrag = null;
Mady Mellor34958fa2016-02-23 09:52:17 -08003712 mCurrIconRow = null;
Mady Mellor97c8df42016-03-22 18:09:39 -07003713 mHandler.removeCallbacks(mFalsingCheck);
Mady Mellor4b80b102016-01-22 08:03:58 -08003714
3715 // Slide back any notifications that might be showing a gear
Mady Mellor7a9b2a62016-03-23 07:41:47 -07003716 resetExposedGearView(true /* animate */, false /* force */);
Mady Mellor4b80b102016-01-22 08:03:58 -08003717
3718 if (currView instanceof ExpandableNotificationRow) {
3719 // Set the listener for the current row's gear
3720 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
3721 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
Mady Mellor4b80b102016-01-22 08:03:58 -08003722 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003723 }
3724
3725 @Override
3726 public void onMoveUpdate(View view, float translation, float delta) {
Mady Mellor97c8df42016-03-22 18:09:39 -07003727 mHandler.removeCallbacks(mFalsingCheck);
3728
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003729 if (mCurrIconRow != null) {
3730 mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
3731
3732 // If the gear is visible and the movement is towards it it's not a location change.
3733 boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
3734 boolean locationChange = isTowardsGear(translation, onLeft)
3735 ? false : mCurrIconRow.isIconLocationChange(translation);
3736 if (locationChange) {
3737 // Don't consider it "snapped" if location has changed.
3738 setSnappedToGear(false);
3739
3740 // Changed directions, make sure we check to fade in icon again.
3741 if (!mHandler.hasCallbacks(mCheckForDrag)) {
3742 // No check scheduled, set null to schedule a new one.
3743 mCheckForDrag = null;
3744 } else {
3745 // Check scheduled, reset alpha and update location; check will fade it in
3746 mCurrIconRow.setGearAlpha(0f);
Mady Mellorcdd57182016-04-12 19:00:17 -07003747 mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003748 }
3749 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003750 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003751
Mady Mellor34958fa2016-02-23 09:52:17 -08003752 final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
3753 && ((ExpandableNotificationRow) view).areGutsExposed();
3754
3755 if (!isPinnedHeadsUp(view) && !gutsExposed) {
3756 // Only show the gear if we're not a heads up view and guts aren't exposed.
3757 checkForDrag();
Mady Mellor4b80b102016-01-22 08:03:58 -08003758 }
3759 }
3760
3761 @Override
Mady Mellordc6c97d2016-03-31 14:18:35 -07003762 public void dismissChild(final View view, float velocity,
3763 boolean useAccelerateInterpolator) {
3764 super.dismissChild(view, velocity, useAccelerateInterpolator);
Selim Cinekd1395642016-04-28 12:22:42 -07003765 if (mIsExpanded) {
3766 // We don't want to quick-dismiss when it's a heads up as this might lead to closing
3767 // of the panel early.
3768 handleChildDismissed(view);
3769 }
Mady Mellor7a9b2a62016-03-23 07:41:47 -07003770 handleGearCoveredOrDismissed();
Mady Mellor4b80b102016-01-22 08:03:58 -08003771 }
3772
3773 @Override
3774 public void snapChild(final View animView, final float targetLeft, float velocity) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003775 super.snapChild(animView, targetLeft, velocity);
3776 onDragCancelled(animView);
3777 if (targetLeft == 0) {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07003778 handleGearCoveredOrDismissed();
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003779 }
3780 }
3781
Mady Mellor7a9b2a62016-03-23 07:41:47 -07003782 private void handleGearCoveredOrDismissed() {
3783 cancelCheckForDrag();
3784 setSnappedToGear(false);
3785 if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
3786 mGearExposedView = null;
3787 }
3788 }
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003789
3790 @Override
3791 public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
3792 float translation) {
3793 if (mCurrIconRow == null) {
3794 cancelCheckForDrag();
3795 return false; // Let SwipeHelper handle it.
3796 }
3797
3798 boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
3799 boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
3800
3801 if (mGearSnappedTo && mCurrIconRow.isVisible()) {
3802 if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
3803 boolean coveringGear =
3804 Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
3805 if (gestureTowardsGear || coveringGear) {
3806 // Gesture is towards or covering the gear
3807 snapChild(animView, 0 /* leftTarget */, velocity);
3808 } else if (isDismissGesture(ev)) {
3809 // Gesture is a dismiss that's not towards the gear
Mady Mellordc6c97d2016-03-31 14:18:35 -07003810 dismissChild(animView, velocity,
3811 !swipedFastEnough() /* useAccelerateInterpolator */);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003812 } else {
3813 // Didn't move enough to dismiss or cover, snap to the gear
3814 snapToGear(animView, velocity);
3815 }
3816 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
3817 || (gestureTowardsGear && !swipedFarEnough())) {
3818 // The gear has been snapped to previously, however, the gear is now on the
3819 // other side. If gesture is towards gear and not too far snap to the gear.
3820 snapToGear(animView, velocity);
3821 } else {
3822 dismissOrSnapBack(animView, velocity, ev);
3823 }
3824 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
3825 || gestureTowardsGear) {
3826 // Gear has not been snapped to previously and this is gear revealing gesture
3827 snapToGear(animView, velocity);
3828 } else {
3829 dismissOrSnapBack(animView, velocity, ev);
3830 }
3831 return true;
3832 }
3833
3834 private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
3835 if (isDismissGesture(ev)) {
Mady Mellordc6c97d2016-03-31 14:18:35 -07003836 dismissChild(animView, velocity,
3837 !swipedFastEnough() /* useAccelerateInterpolator */);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003838 } else {
3839 snapChild(animView, 0 /* leftTarget */, velocity);
3840 }
3841 }
3842
3843 private void snapToGear(View animView, float velocity) {
3844 final float snapBackThreshold = getSpaceForGear(animView);
3845 final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
3846 : -snapBackThreshold;
3847 mGearExposedView = mTranslatingParentView;
Mady Mellor7a9b2a62016-03-23 07:41:47 -07003848 if (animView instanceof ExpandableNotificationRow) {
3849 MetricsLogger.action(mContext, MetricsEvent.ACTION_REVEAL_GEAR,
3850 ((ExpandableNotificationRow) animView).getStatusBarNotification()
3851 .getPackageName());
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003852 }
3853 if (mCurrIconRow != null) {
3854 mCurrIconRow.setSnapping(true);
3855 setSnappedToGear(true);
3856 }
3857 onDragCancelled(animView);
Mady Mellor97c8df42016-03-22 18:09:39 -07003858
3859 // If we're on the lockscreen we want to false this.
3860 if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
3861 mHandler.removeCallbacks(mFalsingCheck);
3862 mHandler.postDelayed(mFalsingCheck, COVER_GEAR_DELAY);
3863 }
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003864 super.snapChild(animView, target, velocity);
3865 }
3866
3867 private boolean swipedEnoughToShowGear(View animView) {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07003868 if (mTranslatingParentView == null) {
3869 return false;
3870 }
Mady Mellorfcba9592016-03-23 11:55:53 -07003871 // If the notification can't be dismissed then how far it can move is
3872 // restricted -- reduce the distance it needs to move in this case.
3873 final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
3874 final float snapBackThreshold = getSpaceForGear(animView) * multiplier;
Mady Mellor4b80b102016-01-22 08:03:58 -08003875 final float translation = getTranslation(animView);
3876 final boolean fromLeft = translation > 0;
3877 final float absTrans = Math.abs(translation);
3878 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
3879
Mady Mellorfcba9592016-03-23 11:55:53 -07003880 return mCurrIconRow.isVisible() && (mCurrIconRow.isIconOnLeft()
3881 ? (translation > snapBackThreshold && translation <= notiThreshold)
3882 : (translation < -snapBackThreshold && translation >= -notiThreshold));
Mady Mellor4b80b102016-01-22 08:03:58 -08003883 }
3884
3885 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -08003886 public Animator getViewTranslationAnimator(View v, float target,
3887 AnimatorUpdateListener listener) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07003888 if (v instanceof ExpandableNotificationRow) {
Mady Mellor34958fa2016-02-23 09:52:17 -08003889 return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
3890 } else {
3891 return super.getViewTranslationAnimator(v, target, listener);
Mady Mellor4b80b102016-01-22 08:03:58 -08003892 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003893 }
3894
3895 @Override
3896 public void setTranslation(View v, float translate) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07003897 ((ExpandableView) v).setTranslation(translate);
Mady Mellor4b80b102016-01-22 08:03:58 -08003898 }
3899
3900 @Override
3901 public float getTranslation(View v) {
Mady Mellor9c2c4962016-04-05 10:43:08 -07003902 return ((ExpandableView) v).getTranslation();
Mady Mellor4b80b102016-01-22 08:03:58 -08003903 }
3904
Mady Mellor7a9b2a62016-03-23 07:41:47 -07003905 public void closeControlsIfOutsideTouch(MotionEvent ev) {
3906 NotificationGuts guts = mPhoneStatusBar.getExposedGuts();
3907 View view = null;
3908 int height = 0;
3909 if (guts != null) {
3910 // Checking guts
3911 view = guts;
3912 height = guts.getActualHeight();
3913 } else if (mCurrIconRow != null && mCurrIconRow.isVisible()
3914 && mTranslatingParentView != null) {
3915 // Checking gear
3916 view = mTranslatingParentView;
3917 height = ((ExpandableView) mTranslatingParentView).getActualHeight();
3918 }
3919 if (view != null) {
3920 final int rx = (int) ev.getRawX();
3921 final int ry = (int) ev.getRawY();
3922
3923 getLocationOnScreen(mTempInt2);
3924 int[] location = new int[2];
3925 view.getLocationOnScreen(location);
3926 final int x = location[0] - mTempInt2[0];
3927 final int y = location[1] - mTempInt2[1];
3928 Rect rect = new Rect(x, y, x + view.getWidth(), y + height);
3929 if (!rect.contains((int) rx, (int) ry)) {
3930 // Touch was outside visible guts / gear notification, close what's visible
3931 mPhoneStatusBar.dismissPopups(-1, -1, true /* resetGear */, true /* animate */);
3932 }
3933 }
3934 }
3935
Mady Mellor4b80b102016-01-22 08:03:58 -08003936 /**
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003937 * Returns whether the gesture is towards the gear location or not.
3938 */
3939 private boolean isTowardsGear(float velocity, boolean onLeft) {
3940 if (mCurrIconRow == null) {
3941 return false;
3942 }
3943 return mCurrIconRow.isVisible()
3944 && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
3945 }
3946
3947 /**
3948 * Indicates the the gear has been snapped to.
3949 */
3950 private void setSnappedToGear(boolean snapped) {
3951 mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
3952 mGearSnappedTo = snapped && mCurrIconRow != null;
3953 }
3954
3955 /**
Mady Mellor4b80b102016-01-22 08:03:58 -08003956 * Returns the horizontal space in pixels required to display the gear behind a
3957 * notification.
3958 */
3959 private float getSpaceForGear(View view) {
3960 if (view instanceof ExpandableNotificationRow) {
3961 return ((ExpandableNotificationRow) view).getSpaceForGear();
3962 }
3963 return 0;
3964 }
3965
3966 private void checkForDrag() {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003967 if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
Mady Mellor4b80b102016-01-22 08:03:58 -08003968 mCheckForDrag = new CheckForDrag();
Mady Mellor97c8df42016-03-22 18:09:39 -07003969 mHandler.postDelayed(mCheckForDrag, SHOW_GEAR_DELAY);
Mady Mellor4b80b102016-01-22 08:03:58 -08003970 }
3971 }
3972
3973 private void cancelCheckForDrag() {
3974 if (mCurrIconRow != null) {
3975 mCurrIconRow.cancelFadeAnimator();
3976 }
3977 mHandler.removeCallbacks(mCheckForDrag);
Mady Mellor4b80b102016-01-22 08:03:58 -08003978 }
3979
3980 private final class CheckForDrag implements Runnable {
3981 @Override
3982 public void run() {
Mady Mellor7a9b2a62016-03-23 07:41:47 -07003983 if (mTranslatingParentView == null) {
3984 return;
3985 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003986 final float translation = getTranslation(mTranslatingParentView);
3987 final float absTransX = Math.abs(translation);
3988 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
3989 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003990 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
3991 || mCurrIconRow.isIconLocationChange(translation)))
3992 && absTransX >= bounceBackToGearWidth * 0.4
Mady Mellor4b80b102016-01-22 08:03:58 -08003993 && absTransX < notiThreshold) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003994 // Fade in the gear
Mady Mellor4b80b102016-01-22 08:03:58 -08003995 mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
3996 notiThreshold);
Mady Mellor4b80b102016-01-22 08:03:58 -08003997 }
3998 }
3999 }
4000
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004001 public void resetExposedGearView(boolean animate, boolean force) {
4002 if (mGearExposedView == null
4003 || (!force && mGearExposedView == mTranslatingParentView)) {
Mady Mellor4b80b102016-01-22 08:03:58 -08004004 // If no gear is showing or it's showing for this view we do nothing.
4005 return;
4006 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004007 final View prevGearExposedView = mGearExposedView;
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004008 if (animate) {
4009 Animator anim = getViewTranslationAnimator(prevGearExposedView,
4010 0 /* leftTarget */, null /* updateListener */);
4011 if (anim != null) {
4012 anim.start();
4013 }
4014 } else if (mGearExposedView instanceof ExpandableNotificationRow) {
4015 ((ExpandableNotificationRow) mGearExposedView).resetTranslation();
4016 }
Mady Mellor4b80b102016-01-22 08:03:58 -08004017 mGearExposedView = null;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00004018 mGearSnappedTo = false;
Mady Mellor4b80b102016-01-22 08:03:58 -08004019 }
4020 }
4021
Selim Cinek33223572016-02-19 19:32:22 -08004022 private void updateContinuousShadowDrawing() {
4023 boolean continuousShadowUpdate = mAnimationRunning
4024 || !mAmbientState.getDraggedViews().isEmpty();
4025 if (continuousShadowUpdate != mContinuousShadowUpdate) {
4026 if (continuousShadowUpdate) {
4027 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
4028 } else {
4029 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
4030 }
Jorim Jaggi38b5ec92016-04-12 01:39:49 -07004031 mContinuousShadowUpdate = continuousShadowUpdate;
Selim Cinek33223572016-02-19 19:32:22 -08004032 }
4033 }
4034
Mady Mellor7a9b2a62016-03-23 07:41:47 -07004035 public void resetExposedGearView(boolean animate, boolean force) {
4036 mSwipeHelper.resetExposedGearView(animate, force);
4037 }
4038
4039 public void closeControlsIfOutsideTouch(MotionEvent ev) {
4040 mSwipeHelper.closeControlsIfOutsideTouch(ev);
4041 }
4042
Jorim Jaggi0dd68812014-05-01 19:17:37 +02004043 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02004044
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004045 static AnimationFilter[] FILTERS = new AnimationFilter[] {
4046
4047 // ANIMATION_TYPE_ADD
4048 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004049 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004050 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004051 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004052 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004053 .animateZ()
4054 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004055
4056 // ANIMATION_TYPE_REMOVE
4057 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004058 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004059 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004060 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004061 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004062 .animateZ()
4063 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004064
4065 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
4066 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004067 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004068 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004069 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004070 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004071 .animateZ()
4072 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004073
4074 // ANIMATION_TYPE_TOP_PADDING_CHANGED
4075 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004076 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004077 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004078 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004079 .animateY()
4080 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004081 .animateZ(),
4082
4083 // ANIMATION_TYPE_START_DRAG
4084 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004085 .animateShadowAlpha(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004086
4087 // ANIMATION_TYPE_SNAP_BACK
4088 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004089 .animateShadowAlpha()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02004090 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004091
4092 // ANIMATION_TYPE_ACTIVATED_CHILD
4093 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004094 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004095
4096 // ANIMATION_TYPE_DIMMED
4097 new AnimationFilter()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004098 .animateDimmed(),
4099
4100 // ANIMATION_TYPE_CHANGE_POSITION
4101 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004102 .animateAlpha() // maybe the children change positions
4103 .animateShadowAlpha()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004104 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02004105 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004106 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04004107 .animateZ(),
4108
4109 // ANIMATION_TYPE_DARK
4110 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01004111 .animateDark()
4112 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004113
4114 // ANIMATION_TYPE_GO_TO_FULL_SHADE
4115 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004116 .animateShadowAlpha()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004117 .animateHeight()
4118 .animateTopInset()
4119 .animateY()
4120 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02004121 .animateZ()
4122 .hasDelays(),
4123
4124 // ANIMATION_TYPE_HIDE_SENSITIVE
4125 new AnimationFilter()
4126 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02004127
4128 // ANIMATION_TYPE_VIEW_RESIZE
4129 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004130 .animateShadowAlpha()
Selim Cineka5e211b2014-08-11 17:35:48 +02004131 .animateHeight()
4132 .animateTopInset()
4133 .animateY()
4134 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02004135
Selim Cinekb5605e52015-02-20 18:21:41 +01004136 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
4137 new AnimationFilter()
4138 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004139 .animateShadowAlpha()
Selim Cinekb5605e52015-02-20 18:21:41 +01004140 .animateHeight()
4141 .animateTopInset()
4142 .animateY()
4143 .animateZ(),
4144
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004145 // ANIMATION_TYPE_HEADS_UP_APPEAR
4146 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004147 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004148 .animateHeight()
4149 .animateTopInset()
4150 .animateY()
4151 .animateZ(),
4152
4153 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
4154 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004155 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004156 .animateHeight()
4157 .animateTopInset()
4158 .animateY()
4159 .animateZ(),
4160
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004161 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
4162 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004163 .animateShadowAlpha()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004164 .animateHeight()
4165 .animateTopInset()
4166 .animateY()
4167 .animateZ()
4168 .hasDelays(),
4169
Selim Cineka59ecc32015-04-07 10:51:49 -07004170 // ANIMATION_TYPE_HEADS_UP_OTHER
4171 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004172 .animateShadowAlpha()
Selim Cineka59ecc32015-04-07 10:51:49 -07004173 .animateHeight()
4174 .animateTopInset()
4175 .animateY()
4176 .animateZ(),
4177
Selim Cinekd9acca52014-09-01 22:33:25 +02004178 // ANIMATION_TYPE_EVERYTHING
4179 new AnimationFilter()
4180 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08004181 .animateShadowAlpha()
Selim Cinekd9acca52014-09-01 22:33:25 +02004182 .animateDark()
Selim Cinekd9acca52014-09-01 22:33:25 +02004183 .animateDimmed()
4184 .animateHideSensitive()
4185 .animateHeight()
4186 .animateTopInset()
4187 .animateY()
4188 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004189 };
4190
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004191 static int[] LENGTHS = new int[] {
4192
4193 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004194 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004195
4196 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004197 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004198
4199 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
4200 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4201
4202 // ANIMATION_TYPE_TOP_PADDING_CHANGED
4203 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4204
4205 // ANIMATION_TYPE_START_DRAG
4206 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4207
4208 // ANIMATION_TYPE_SNAP_BACK
4209 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4210
4211 // ANIMATION_TYPE_ACTIVATED_CHILD
4212 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
4213
4214 // ANIMATION_TYPE_DIMMED
4215 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004216
4217 // ANIMATION_TYPE_CHANGE_POSITION
4218 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04004219
4220 // ANIMATION_TYPE_DARK
4221 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004222
4223 // ANIMATION_TYPE_GO_TO_FULL_SHADE
4224 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02004225
4226 // ANIMATION_TYPE_HIDE_SENSITIVE
4227 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02004228
4229 // ANIMATION_TYPE_VIEW_RESIZE
4230 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02004231
Selim Cinekb5605e52015-02-20 18:21:41 +01004232 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08004233 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01004234
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004235 // ANIMATION_TYPE_HEADS_UP_APPEAR
4236 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
4237
4238 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
4239 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
4240
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004241 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
4242 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
4243
Selim Cineka59ecc32015-04-07 10:51:49 -07004244 // ANIMATION_TYPE_HEADS_UP_OTHER
4245 StackStateAnimator.ANIMATION_DURATION_STANDARD,
4246
Selim Cinekd9acca52014-09-01 22:33:25 +02004247 // ANIMATION_TYPE_EVERYTHING
4248 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004249 };
4250
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004251 static final int ANIMATION_TYPE_ADD = 0;
4252 static final int ANIMATION_TYPE_REMOVE = 1;
4253 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
4254 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
4255 static final int ANIMATION_TYPE_START_DRAG = 4;
4256 static final int ANIMATION_TYPE_SNAP_BACK = 5;
4257 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
4258 static final int ANIMATION_TYPE_DIMMED = 7;
4259 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04004260 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004261 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02004262 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02004263 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01004264 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07004265 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
4266 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07004267 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
4268 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
4269 static final int ANIMATION_TYPE_EVERYTHING = 18;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02004270
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004271 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
4272 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
4273
Selim Cinek572bbd42014-04-25 16:43:27 +02004274 final long eventStartTime;
4275 final View changingView;
4276 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004277 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004278 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02004279 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01004280 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07004281 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02004282
Jorim Jaggi0dd68812014-05-01 19:17:37 +02004283 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02004284 this(view, type, LENGTHS[type]);
4285 }
4286
4287 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02004288 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
4289 changingView = view;
4290 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02004291 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02004292 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004293 }
4294
4295 /**
4296 * Combines the length of several animation events into a single value.
4297 *
4298 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004299 * @return The combined length. Depending on the event types, this might be the maximum of
4300 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004301 */
4302 static long combineLength(ArrayList<AnimationEvent> events) {
4303 long length = 0;
4304 int size = events.size();
4305 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02004306 AnimationEvent event = events.get(i);
4307 length = Math.max(length, event.length);
4308 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
4309 return event.length;
4310 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02004311 }
4312 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02004313 }
4314 }
4315
Selim Cinek67b22602014-03-10 15:40:16 +01004316}