blob: 85b14264837d6a1aef9da4d9420dcc719f5b4036 [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;
Mady Mellor4b80b102016-01-22 08:03:58 -080021import android.animation.AnimatorSet;
Selim Cinek614576e2016-01-20 10:54:09 -080022import android.animation.ObjectAnimator;
23import android.animation.PropertyValuesHolder;
Selim Cinekd35c2792016-01-21 13:20:57 -080024import android.animation.TimeAnimator;
25import android.animation.ValueAnimator;
Mady Mellor4b80b102016-01-22 08:03:58 -080026import android.animation.ValueAnimator.AnimatorUpdateListener;
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;
39import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070040import android.util.Pair;
Selim Cinek67b22602014-03-10 15:40:16 +010041import android.view.MotionEvent;
42import android.view.VelocityTracker;
43import android.view.View;
44import android.view.ViewConfiguration;
45import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020046import android.view.ViewTreeObserver;
Selim Cinek572bbd42014-04-25 16:43:27 +020047import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080048import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010049import android.widget.OverScroller;
Jorim Jaggi56306252014-07-03 00:40:09 +020050
Selim Cinek67b22602014-03-10 15:40:16 +010051import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080052import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010053import com.android.systemui.R;
54import com.android.systemui.SwipeHelper;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070055import com.android.systemui.classifier.FalsingManager;
Selim Cineka32ab602014-06-11 15:06:01 +020056import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040057import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020058import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010059import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020060import com.android.systemui.statusbar.ExpandableView;
Selim Cinek2cd45df2015-06-09 18:00:07 -070061import com.android.systemui.statusbar.NotificationOverflowContainer;
Mady Mellor4b80b102016-01-22 08:03:58 -080062import com.android.systemui.statusbar.NotificationSettingsIconRow;
63import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010064import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020065import com.android.systemui.statusbar.StatusBarState;
Selim Cinek33223572016-02-19 19:32:22 -080066import com.android.systemui.statusbar.notification.FakeShadowView;
Selim Cinek42357e02016-02-24 18:48:01 -080067import com.android.systemui.statusbar.notification.NotificationUtils;
Selim Cinekb5605e52015-02-20 18:21:41 +010068import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020069import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070070import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070071import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010072import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010073
Selim Cinek572bbd42014-04-25 16:43:27 +020074import java.util.ArrayList;
Selim Cinek33223572016-02-19 19:32:22 -080075import java.util.Collections;
76import java.util.Comparator;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020077import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020078
Selim Cinek67b22602014-03-10 15:40:16 +010079/**
80 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
81 */
82public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020083 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Mady Mellor4b80b102016-01-22 08:03:58 -080084 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
85 SettingsIconRowListener {
Selim Cinek67b22602014-03-10 15:40:16 +010086
Selim Cinekd35c2792016-01-21 13:20:57 -080087 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -080088 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +010089 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020090 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
91 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020092 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010093
94 /**
95 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
96 */
97 private static final int INVALID_POINTER = -1;
98
Selim Cinek1408eb52014-06-02 14:45:38 +020099 private ExpandHelper mExpandHelper;
Selim Cinek33223572016-02-19 19:32:22 -0800100 private NotificationSwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +0200101 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +0100102 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -0800103 private final Paint mBackgroundPaint = new Paint();
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100104
105 /**
106 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
107 * externally from {@link #setStackHeight}
108 */
109 private float mLastSetStackHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100110 private int mOwnScrollY;
111 private int mMaxLayoutHeight;
112
113 private VelocityTracker mVelocityTracker;
114 private OverScroller mScroller;
115 private int mTouchSlop;
116 private int mMinimumVelocity;
117 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100118 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200119 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100120 private boolean mIsBeingDragged;
121 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200122 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +0100123 private int mActivePointerId;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100124 private boolean mTouchIsClick;
125 private float mInitialTouchX;
126 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100127
Selim Cinek67b22602014-03-10 15:40:16 +0100128 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100129 private int mContentHeight;
130 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200131 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100132 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100133 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800134 private int mIncreasedPaddingBetweenElements;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200135 private int mTopPadding;
Selim Cinekd83771e2014-07-04 16:45:31 +0200136 private int mCollapseSecondCardPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100137
138 /**
139 * The algorithm which calculates the properties for our children
140 */
Selim Cinekaf0dc312015-12-15 17:01:44 -0800141 private final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100142
143 /**
144 * The current State this Layout is in
145 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200146 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200147 private AmbientState mAmbientState = new AmbientState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100148 private NotificationGroupManager mGroupManager;
Selim Cinek3776fe02016-02-04 13:32:43 -0800149 private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700150 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
151 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
152 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
153 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
154 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200155 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700156 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
157 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200158 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200159 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200160 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800161 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200162
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200163 /**
164 * The raw amount of the overScroll on the top, which is not rubber-banded.
165 */
166 private float mOverScrolledTopPixels;
167
168 /**
169 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
170 */
171 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200172 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200173 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200174 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100175 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200176 private boolean mNeedsAnimation;
177 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200178 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200179 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400180 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100181 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200182 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200183 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200184 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200185 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200186 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700187 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200188 private boolean mExpandingNotification;
189 private boolean mExpandedInThisMotion;
190 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400191 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200192 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400193 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200194
195 /**
196 * Was the scroller scrolled to the top when the down motion was observed?
197 */
198 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200199 /**
200 * The minimal amount of over scroll which is needed in order to switch to the quick settings
201 * when over scrolling on a expanded card.
202 */
203 private float mMinTopOverScrollToEscape;
204 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700205 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200206 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200207 private boolean mDontReportNextOverScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200208 private boolean mRequestViewResizeAnimationOnLayout;
209 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100210 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700211 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700212
Selim Cinek1408eb52014-06-02 14:45:38 +0200213 /**
214 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
215 * This is needed to avoid scrolling too far after the notification was collapsed in the same
216 * motion.
217 */
218 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400219 private SwipeHelper.LongPressListener mLongPressListener;
Mady Mellor4b80b102016-01-22 08:03:58 -0800220 private GearDisplayedListener mGearDisplayedListener;
221
222 private NotificationSettingsIconRow mCurrIconRow;
223 private View mTranslatingParentView;
224 private View mGearExposedView;
Selim Cinek1408eb52014-06-02 14:45:38 +0200225
226 /**
227 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
228 * animating.
229 */
230 private boolean mOnlyScrollingInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200231 private boolean mInterceptDelegateEnabled;
232 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700233 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700234 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200235 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200236 = new ViewTreeObserver.OnPreDrawListener() {
237 @Override
238 public boolean onPreDraw() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200239 updateChildren();
240 mChildrenUpdateRequested = false;
241 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200242 return true;
243 }
244 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200245 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100246 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100247 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700248 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek0fccc722015-07-29 17:04:36 -0700249 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700250 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
251 = new HashSet<>();
252 private HeadsUpManager mHeadsUpManager;
253 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700254 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200255 private boolean mForceNoOverlappingRendering;
Selim Cinek2cd45df2015-06-09 18:00:07 -0700256 private NotificationOverflowContainer mOverflowContainer;
Selim Cineke0890e52015-06-17 11:17:08 -0700257 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700258 private FalsingManager mFalsingManager;
Selim Cinek6811d722016-01-19 17:53:12 -0800259 private boolean mAnimationRunning;
260 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
261 = new ViewTreeObserver.OnPreDrawListener() {
262 @Override
263 public boolean onPreDraw() {
Selim Cinek614576e2016-01-20 10:54:09 -0800264 // if it needs animation
265 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
266 updateBackground();
267 }
Selim Cinek6811d722016-01-19 17:53:12 -0800268 return true;
269 }
270 };
271 private Rect mBackgroundBounds = new Rect();
Selim Cinek614576e2016-01-20 10:54:09 -0800272 private Rect mStartAnimationRect = new Rect();
273 private Rect mEndAnimationRect = new Rect();
Selim Cinekd35c2792016-01-21 13:20:57 -0800274 private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
Selim Cinek614576e2016-01-20 10:54:09 -0800275 private boolean mAnimateNextBackgroundBottom;
276 private boolean mAnimateNextBackgroundTop;
277 private ObjectAnimator mBottomAnimator = null;
278 private ObjectAnimator mTopAnimator = null;
279 private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
280 private ActivatableNotificationView mLastVisibleBackgroundChild = null;
Selim Cinekd35c2792016-01-21 13:20:57 -0800281 private int mBgColor;
282 private float mDimAmount;
283 private ValueAnimator mDimAnimator;
Selim Cinek33223572016-02-19 19:32:22 -0800284 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
Selim Cinekd35c2792016-01-21 13:20:57 -0800285 private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
286 @Override
287 public void onAnimationEnd(Animator animation) {
288 mDimAnimator = null;
289 }
290 };
291 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
292 = new ValueAnimator.AnimatorUpdateListener() {
293
294 @Override
295 public void onAnimationUpdate(ValueAnimator animation) {
296 setDimAmount((Float) animation.getAnimatedValue());
297 }
298 };
Jason Monk16ac3772016-02-10 15:39:21 -0500299 private ViewGroup mQsContainer;
Selim Cinek33223572016-02-19 19:32:22 -0800300 private boolean mContinuousShadowUpdate;
301 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
302 = new ViewTreeObserver.OnPreDrawListener() {
303
304 @Override
305 public boolean onPreDraw() {
306 updateViewShadows();
307 return true;
308 }
309 };
310 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
311 @Override
312 public int compare(ExpandableView view, ExpandableView otherView) {
313 float endY = view.getTranslationY() + view.getActualHeight();
314 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
315 if (endY < otherEndY) {
316 return -1;
317 } else if (endY > otherEndY) {
318 return 1;
319 } else {
320 // The two notifications end at the same location
321 return 0;
322 }
323 }
324 };
Selim Cinek67b22602014-03-10 15:40:16 +0100325
326 public NotificationStackScrollLayout(Context context) {
327 this(context, null);
328 }
329
330 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
331 this(context, attrs, 0);
332 }
333
334 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
335 this(context, attrs, defStyleAttr, 0);
336 }
337
338 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
339 int defStyleRes) {
340 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinekd35c2792016-01-21 13:20:57 -0800341 mBgColor = context.getColor(R.color.notification_shade_background_color);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200342 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
343 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
344 mExpandHelper = new ExpandHelper(getContext(), this,
345 minHeight, maxHeight);
346 mExpandHelper.setEventSource(this);
347 mExpandHelper.setScrollAdapter(this);
Mady Mellor4b80b102016-01-22 08:03:58 -0800348 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
Selim Cinek1cf41c12014-08-12 20:06:19 +0200349 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800350 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100351 initView(context);
Selim Cinek6811d722016-01-19 17:53:12 -0800352 setWillNotDraw(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100353 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100354 mDebugPaint = new Paint();
355 mDebugPaint.setColor(0xffff0000);
356 mDebugPaint.setStrokeWidth(2);
357 mDebugPaint.setStyle(Paint.Style.STROKE);
358 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700359 mFalsingManager = FalsingManager.getInstance(context);
Selim Cinekd35c2792016-01-21 13:20:57 -0800360 mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
Selim Cinek67b22602014-03-10 15:40:16 +0100361 }
362
363 @Override
Mady Mellorb53bc272016-02-11 18:28:23 -0800364 public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800365 if (mLongPressListener != null) {
Mady Mellorb53bc272016-02-11 18:28:23 -0800366 mLongPressListener.onLongPress(row, x, y);
Mady Mellor4b80b102016-01-22 08:03:58 -0800367 }
368 }
369
370 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100371 protected void onDraw(Canvas canvas) {
Selim Cinekd35c2792016-01-21 13:20:57 -0800372 canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100373 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800374 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100375 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200376 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200377 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200378 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
379 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100380 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
381 y = (int) getLayoutHeight();
382 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200383 y = getHeight() - getEmptyBottomMargin();
384 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100385 }
386 }
387
Selim Cinekd35c2792016-01-21 13:20:57 -0800388 private void updateBackgroundDimming() {
389 float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
390 // We need to manually blend in the background color
391 int scrimColor = mScrimController.getScrimBehindColor();
392 // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
393 float alphaInv = 1 - alpha;
394 int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
395 (int) (Color.red(mBgColor) + alphaInv * Color.red(scrimColor)),
396 (int) (Color.green(mBgColor) + alphaInv * Color.green(scrimColor)),
397 (int) (Color.blue(mBgColor) + alphaInv * Color.blue(scrimColor)));
398 mBackgroundPaint.setColor(color);
399 invalidate();
400 }
401
Selim Cinek67b22602014-03-10 15:40:16 +0100402 private void initView(Context context) {
403 mScroller = new OverScroller(getContext());
404 setFocusable(true);
405 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200406 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100407 final ViewConfiguration configuration = ViewConfiguration.get(context);
408 mTouchSlop = configuration.getScaledTouchSlop();
409 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
410 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100411 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100412 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200413 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100414 mBottomStackPeekSize = context.getResources()
415 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800416 mStackScrollAlgorithm.initView(context);
Selim Cinek61633a82016-01-25 15:54:10 -0800417 mPaddingBetweenElements = Math.max(1, context.getResources()
Selim Cinekcacc6042016-01-21 16:16:41 -0800418 .getDimensionPixelSize(R.dimen.notification_divider_height));
Selim Cinek61633a82016-01-25 15:54:10 -0800419 mIncreasedPaddingBetweenElements = context.getResources()
420 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
421
Selim Cinek587cbf32016-01-19 11:36:18 -0800422 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
Selim Cinek1408eb52014-06-02 14:45:38 +0200423 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
424 R.dimen.min_top_overscroll_to_qs);
Selim Cinekd83771e2014-07-04 16:45:31 +0200425 mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
426 R.dimen.notification_collapse_second_card_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200427 }
428
Selim Cinekaef92ef2014-06-06 18:06:04 +0200429 private void notifyHeightChangeListener(ExpandableView view) {
430 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100431 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200432 }
Selim Cinek67b22602014-03-10 15:40:16 +0100433 }
434
435 @Override
436 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
437 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinek924c6122016-01-15 18:24:05 -0800438 measureChildren(widthMeasureSpec, heightMeasureSpec);
Selim Cinek67b22602014-03-10 15:40:16 +0100439 }
440
441 @Override
442 protected void onLayout(boolean changed, int l, int t, int r, int b) {
443
444 // we layout all our children centered on the top
445 float centerX = getWidth() / 2.0f;
446 for (int i = 0; i < getChildCount(); i++) {
447 View child = getChildAt(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100448 if (child.getVisibility() == GONE) {
449 continue;
450 }
Selim Cinek67b22602014-03-10 15:40:16 +0100451 float width = child.getMeasuredWidth();
452 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100453 child.layout((int) (centerX - width / 2.0f),
454 0,
455 (int) (centerX + width / 2.0f),
456 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100457 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200458 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100459 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200460 clampScrollPosition();
Selim Cinekb5605e52015-02-20 18:21:41 +0100461 if (mRequestViewResizeAnimationOnLayout) {
Selim Cinek5bc852a2015-12-21 12:19:09 -0800462 requestAnimationOnViewResize(null);
Selim Cinekb5605e52015-02-20 18:21:41 +0100463 mRequestViewResizeAnimationOnLayout = false;
464 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200465 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -0800466 updateFirstAndLastBackgroundViews();
Selim Cinek67b22602014-03-10 15:40:16 +0100467 }
468
Selim Cinek5bc852a2015-12-21 12:19:09 -0800469 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
470 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200471 mNeedViewResizeAnimation = true;
472 mNeedsAnimation = true;
473 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200474 }
475
Selim Cinekc27437b2014-05-14 10:23:33 +0200476 public void updateSpeedBumpIndex(int newIndex) {
Selim Cinekc27437b2014-05-14 10:23:33 +0200477 mAmbientState.setSpeedBumpIndex(newIndex);
478 }
479
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200480 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
481 mListener = listener;
482 }
483
484 /**
485 * Returns the location the given child is currently rendered at.
486 *
487 * @param child the child to get the location for
Selim Cinekb036ca42015-02-20 15:56:28 +0100488 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200489 */
490 public int getChildLocation(View child) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100491 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200492 if (childViewState == null) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100493 return StackViewState.LOCATION_UNKNOWN;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200494 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100495 if (childViewState.gone) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100496 return StackViewState.LOCATION_GONE;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100497 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200498 return childViewState.location;
499 }
500
Selim Cinek67b22602014-03-10 15:40:16 +0100501 private void setMaxLayoutHeight(int maxLayoutHeight) {
502 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200503 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100504 }
505
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200506 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700507 mAmbientState.setLayoutHeight(getLayoutHeight());
508 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100509 }
510
511 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +0200512 * @return whether the height of the layout needs to be adapted, in order to ensure that the
513 * last child is not in the bottom stack.
514 */
515 private boolean needsHeightAdaption() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200516 return getNotGoneChildCount() > 1;
Selim Cinek4a1ac842014-05-01 15:51:58 +0200517 }
518
Selim Cinek4a1ac842014-05-01 15:51:58 +0200519 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100520 * Updates the children views according to the stack scroll algorithm. Call this whenever
521 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
522 */
523 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -0800524 updateScrollStateForAddedChildren();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200525 mAmbientState.setScrollY(mOwnScrollY);
526 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200527 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200528 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100529 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200530 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100531 }
532 }
533
Selim Cinek3776fe02016-02-04 13:32:43 -0800534 private void updateScrollStateForAddedChildren() {
535 if (mChildrenToAddAnimated.isEmpty()) {
536 return;
537 }
538 for (int i = 0; i < getChildCount(); i++) {
539 ExpandableView child = (ExpandableView) getChildAt(i);
540 if (mChildrenToAddAnimated.contains(child)) {
541 int startingPosition = getPositionInLinearLayout(child);
Selim Cinek42357e02016-02-24 18:48:01 -0800542 int padding = child.getIncreasedPaddingAmount() == 1.0f
Selim Cinek3776fe02016-02-04 13:32:43 -0800543 ? mIncreasedPaddingBetweenElements :
544 mPaddingBetweenElements;
545 int childHeight = getIntrinsicHeight(child) + padding;
546 if (startingPosition < mOwnScrollY) {
547 // This child starts off screen, so let's keep it offscreen to keep the others visible
548
549 mOwnScrollY += childHeight;
550 }
551 }
552 }
553 clampScrollPosition();
554 }
555
Selim Cinek319bdc42014-05-01 23:01:58 +0200556 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200557 if (!mChildrenUpdateRequested) {
558 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
559 mChildrenUpdateRequested = true;
560 invalidate();
561 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200562 }
563
Selim Cinek67b22602014-03-10 15:40:16 +0100564 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200565 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100566 }
567
Selim Cinekf7a14c02014-07-07 14:01:46 +0200568 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100569 int scrollRange = getScrollRange();
570 if (scrollRange < mOwnScrollY) {
571 mOwnScrollY = scrollRange;
572 }
573 }
574
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200575 public int getTopPadding() {
576 return mTopPadding;
577 }
578
Selim Cinek1408eb52014-06-02 14:45:38 +0200579 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200580 if (mTopPadding != topPadding) {
581 mTopPadding = topPadding;
582 updateAlgorithmHeightAndPadding();
583 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200584 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200585 mTopPaddingNeedsAnimation = true;
586 mNeedsAnimation = true;
587 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200588 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200589 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200590 }
591 }
592
593 /**
594 * Update the height of the stack to a new height.
595 *
596 * @param height the new height of the stack
597 */
598 public void setStackHeight(float height) {
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100599 mLastSetStackHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200600 setIsExpanded(height > 0.0f);
601 int newStackHeight = (int) height;
Selim Cinekd83771e2014-07-04 16:45:31 +0200602 int minStackHeight = getMinStackHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200603 int stackHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700604 float paddingOffset;
Selim Cinek131c1e22015-05-11 19:04:49 -0700605 boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
Selim Cinek31aada42015-12-18 17:51:15 -0800606 int normalUnfoldPositionStart = trackingHeadsUp
607 ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700608 : minStackHeight;
Selim Cinek684a4422015-04-15 16:18:39 -0700609 if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
Jorim Jaggi58bef332014-11-21 18:10:32 +0100610 || getNotGoneChildCount() == 0) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700611 paddingOffset = mTopPaddingOverflow;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200612 stackHeight = newStackHeight;
613 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800614 int translationY;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700615 if (!trackingHeadsUp) {
Selim Cinek31aada42015-12-18 17:51:15 -0800616 // We did not reach the position yet where we actually start growing,
617 // so we translate the stack upwards.
618 translationY = (newStackHeight - minStackHeight);
619 // A slight parallax effect is introduced in order for the stack to catch up with
620 // the top card.
621 float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
622 / minStackHeight;
623 partiallyThere = Math.max(0, partiallyThere);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700624 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
625 mCollapseSecondCardPadding);
626 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800627 translationY = (int) (height - normalUnfoldPositionStart);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700628 }
629 paddingOffset = translationY - mTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200630 stackHeight = (int) (height - (translationY - mTopPadding));
631 }
632 if (stackHeight != mCurrentStackHeight) {
633 mCurrentStackHeight = stackHeight;
634 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200635 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200636 }
Selim Cinekd2281152015-04-10 14:37:46 -0700637 setStackTranslation(paddingOffset);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700638 }
639
Selim Cinekd2281152015-04-10 14:37:46 -0700640 public float getStackTranslation() {
641 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700642 }
643
Selim Cinekd2281152015-04-10 14:37:46 -0700644 private void setStackTranslation(float stackTranslation) {
645 if (stackTranslation != mStackTranslation) {
646 mStackTranslation = stackTranslation;
647 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700648 requestChildrenUpdate();
649 }
Selim Cinek67b22602014-03-10 15:40:16 +0100650 }
651
652 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100653 * 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 +0100654 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
655 *
656 * @return either the layout height or the externally defined height, whichever is smaller
657 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200658 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100659 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
660 }
661
Selim Cinek816c8e42015-11-19 12:00:45 -0800662 public int getFirstItemMinHeight() {
663 final ExpandableView firstChild = getFirstChildNotGone();
664 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100665 }
666
667 public int getBottomStackPeekSize() {
668 return mBottomStackPeekSize;
669 }
670
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200671 public int getCollapseSecondCardPadding() {
672 return mCollapseSecondCardPadding;
673 }
674
Dan Sandler4247a5c2014-07-23 15:58:08 -0400675 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100676 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400677 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100678 }
679
Mady Mellor4b80b102016-01-22 08:03:58 -0800680 public void setGearDisplayedListener(GearDisplayedListener listener) {
681 mGearDisplayedListener = listener;
682 }
683
Jason Monk16ac3772016-02-10 15:39:21 -0500684 public void setQsContainer(ViewGroup qsContainer) {
685 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +0200686 }
687
Selim Cinek67b22602014-03-10 15:40:16 +0100688 public void onChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400689 if (mDismissAllInProgress) {
690 return;
691 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100692 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200693 if (mDragAnimPendingChildren.contains(v)) {
694 // We start the swipe and finish it in the same frame, we don't want any animation
695 // for the drag
696 mDragAnimPendingChildren.remove(v);
697 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200698 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200699 mAmbientState.onDragFinished(v);
Selim Cinek33223572016-02-19 19:32:22 -0800700 updateContinuousShadowDrawing();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700701 if (v instanceof ExpandableNotificationRow) {
702 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
703 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700704 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700705 }
706 }
707 final View veto = v.findViewById(R.id.veto);
708 if (veto != null && veto.getVisibility() != View.GONE) {
709 veto.performClick();
710 }
711 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
Blazej Magnowski72323322015-07-24 11:49:40 -0700712
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700713 mFalsingManager.onNotificationDismissed();
714 if (mFalsingManager.shouldEnforceBouncer()) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700715 mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
716 false /* dismissShade */, true /* afterKeyguardGone */);
717 }
Selim Cinekeb973562014-05-02 17:07:49 +0200718 }
719
720 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800721 public void onChildSnappedBack(View animView, float targetLeft) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200722 mAmbientState.onDragFinished(animView);
Selim Cinek33223572016-02-19 19:32:22 -0800723 updateContinuousShadowDrawing();
Selim Cinekeb973562014-05-02 17:07:49 +0200724 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200725 if (mAnimationsEnabled) {
726 mSnappedBackChildren.add(animView);
727 mNeedsAnimation = true;
728 }
Selim Cinekeb973562014-05-02 17:07:49 +0200729 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200730 } else {
731 // We start the swipe and snap back in the same frame, we don't want any animation
732 mDragAnimPendingChildren.remove(animView);
733 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800734
735 if (targetLeft == 0 && mCurrIconRow != null) {
736 mCurrIconRow.resetState();
737 if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
738 mGearExposedView = null;
739 }
740 }
Selim Cinek67b22602014-03-10 15:40:16 +0100741 }
742
Adrian Roos5d9cc662014-05-28 17:08:13 +0200743 @Override
744 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700745 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700746 mScrimController.setTopHeadsUpDragAmount(animView,
747 Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
748 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800749 return true; // Don't fade out the notification
Adrian Roos5d9cc662014-05-28 17:08:13 +0200750 }
751
Selim Cinek67b22602014-03-10 15:40:16 +0100752 public void onBeginDrag(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700753 mFalsingManager.onNotificatonStartDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100754 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200755 mAmbientState.onBeginDrag(v);
Selim Cinek33223572016-02-19 19:32:22 -0800756 updateContinuousShadowDrawing();
Selim Cinek131c1e22015-05-11 19:04:49 -0700757 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200758 mDragAnimPendingChildren.add(v);
759 mNeedsAnimation = true;
760 }
Selim Cinekeb973562014-05-02 17:07:49 +0200761 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100762 }
763
Selim Cinek684a4422015-04-15 16:18:39 -0700764 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700765 if (v instanceof ExpandableNotificationRow) {
766 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700767 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700768 }
769 return false;
770 }
771
772 private boolean isHeadsUp(View v) {
773 if (v instanceof ExpandableNotificationRow) {
774 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
775 return row.isHeadsUp();
776 }
777 return false;
778 }
779
Selim Cinek67b22602014-03-10 15:40:16 +0100780 public void onDragCancelled(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700781 mFalsingManager.onNotificatonStopDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100782 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100783 }
784
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700785 @Override
786 public float getFalsingThresholdFactor() {
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700787 return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700788 }
789
Mady Mellor4b80b102016-01-22 08:03:58 -0800790 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100791 public View getChildAtPosition(MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800792 View child = getChildAtPosition(ev.getX(), ev.getY());
793 if (child instanceof ExpandableNotificationRow) {
794 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
795 ExpandableNotificationRow parent = row.getNotificationParent();
796 if (mGearExposedView != null && parent != null
797 && parent.areChildrenExpanded() && mGearExposedView == parent) {
798 // In this case the group is expanded and showing the gear for the
799 // group, further interaction should apply to the group, not any
800 // child notifications so we use the parent of the child.
801 child = row.getNotificationParent();
802 }
803 }
804 return child;
Selim Cinek67b22602014-03-10 15:40:16 +0100805 }
806
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100807 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
808 getLocationOnScreen(mTempInt2);
809 float localTouchY = touchY - mTempInt2[1];
810
811 ExpandableView closestChild = null;
812 float minDist = Float.MAX_VALUE;
813
814 // find the view closest to the location, accounting for GONE views
815 final int count = getChildCount();
816 for (int childIdx = 0; childIdx < count; childIdx++) {
817 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
818 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800819 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100820 continue;
821 }
822 float childTop = slidingChild.getTranslationY();
823 float top = childTop + slidingChild.getClipTopAmount();
824 float bottom = childTop + slidingChild.getActualHeight();
825
826 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
827 if (dist < minDist) {
828 closestChild = slidingChild;
829 minDist = dist;
830 }
831 }
832 return closestChild;
833 }
834
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200835 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100836 getLocationOnScreen(mTempInt2);
837 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100838 }
839
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200840 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100841 // find the view under the pointer, accounting for GONE views
842 final int count = getChildCount();
843 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200844 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100845 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800846 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100847 continue;
848 }
Selim Cinek89faff12014-06-19 16:29:04 -0700849 float childTop = slidingChild.getTranslationY();
850 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200851 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200852
853 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
854 // camera affordance).
855 int left = 0;
856 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100857
858 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100859 if (slidingChild instanceof ExpandableNotificationRow) {
860 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -0700861 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cinek5bc852a2015-12-21 12:19:09 -0800862 && mHeadsUpManager.getTopEntry().entry.row != row
863 && mGroupManager.getGroupSummary(
864 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
865 != row) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700866 continue;
867 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100868 return row.getViewAtPosition(touchY - childTop);
869 }
Selim Cinek67b22602014-03-10 15:40:16 +0100870 return slidingChild;
871 }
872 }
873 return null;
874 }
875
876 public boolean canChildBeExpanded(View v) {
877 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -0700878 && ((ExpandableNotificationRow) v).isExpandable()
879 && !((ExpandableNotificationRow) v).isHeadsUp();
Selim Cinek67b22602014-03-10 15:40:16 +0100880 }
881
882 public void setUserExpandedChild(View v, boolean userExpanded) {
883 if (v instanceof ExpandableNotificationRow) {
Selim Cinek388df6d2015-10-22 13:25:11 -0700884 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded,
885 true /* allowChildrenExpansion */);
Selim Cinek67b22602014-03-10 15:40:16 +0100886 }
887 }
888
889 public void setUserLockedChild(View v, boolean userLocked) {
890 if (v instanceof ExpandableNotificationRow) {
891 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
892 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200893 removeLongPressCallback();
894 requestDisallowInterceptTouchEvent(true);
895 }
896
897 @Override
898 public void expansionStateChanged(boolean isExpanding) {
899 mExpandingNotification = isExpanding;
900 if (!mExpandedInThisMotion) {
901 mMaxScrollAfterExpand = mOwnScrollY;
902 mExpandedInThisMotion = true;
903 }
904 }
905
906 public void setScrollingEnabled(boolean enable) {
907 mScrollingEnabled = enable;
908 }
909
910 public void setExpandingEnabled(boolean enable) {
911 mExpandHelper.setEnabled(enable);
912 }
913
914 private boolean isScrollingEnabled() {
915 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100916 }
917
Selim Cinek67b22602014-03-10 15:40:16 +0100918 public boolean canChildBeDismissed(View v) {
Selim Cinek9c17b772015-07-07 20:37:09 -0700919 return StackScrollAlgorithm.canChildBeDismissed(v);
Selim Cinek67b22602014-03-10 15:40:16 +0100920 }
921
Selim Cinek19c8c702014-08-25 22:09:19 +0200922 @Override
923 public boolean isAntiFalsingNeeded() {
Selim Cinekcb2b6732014-09-05 16:17:22 +0200924 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +0200925 }
926
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100927 private void setSwipingInProgress(boolean isSwiped) {
928 mSwipingInProgress = isSwiped;
929 if(isSwiped) {
930 requestDisallowInterceptTouchEvent(true);
931 }
Selim Cinek67b22602014-03-10 15:40:16 +0100932 }
933
934 @Override
935 protected void onConfigurationChanged(Configuration newConfig) {
936 super.onConfigurationChanged(newConfig);
937 float densityScale = getResources().getDisplayMetrics().density;
938 mSwipeHelper.setDensityScale(densityScale);
939 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
940 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
941 initView(getContext());
942 }
943
Dan Sandlereceda3d2014-07-21 15:35:01 -0400944 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400945 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
Selim Cinek67b22602014-03-10 15:40:16 +0100946 }
947
948 @Override
949 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200950 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
951 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100952 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +0200953 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +0200954 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200955 if (isCancelOrUp) {
956 mExpandHelper.onlyObserveMovements(false);
957 }
958 boolean wasExpandingBefore = mExpandingNotification;
959 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +0200960 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
961 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200962 dispatchDownEventToScroller(ev);
963 }
964 }
Selim Cinek67b22602014-03-10 15:40:16 +0100965 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700966 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
967 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100968 scrollerWantsIt = onScrollTouch(ev);
969 }
970 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200971 if (!mIsBeingDragged
972 && !mExpandingNotification
973 && !mExpandedInThisMotion
974 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100975 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
976 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200977 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
978 }
979
980 private void dispatchDownEventToScroller(MotionEvent ev) {
981 MotionEvent downEvent = MotionEvent.obtain(ev);
982 downEvent.setAction(MotionEvent.ACTION_DOWN);
983 onScrollTouch(downEvent);
984 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +0100985 }
986
987 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200988 if (!isScrollingEnabled()) {
989 return false;
990 }
Jason Monk16ac3772016-02-10 15:39:21 -0500991 if (ev.getY() < mQsContainer.getBottom()) {
992 return false;
993 }
Selim Cinek67b22602014-03-10 15:40:16 +0100994 initVelocityTrackerIfNotExists();
995 mVelocityTracker.addMovement(ev);
996
997 final int action = ev.getAction();
998
999 switch (action & MotionEvent.ACTION_MASK) {
1000 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +02001001 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +01001002 return false;
1003 }
1004 boolean isBeingDragged = !mScroller.isFinished();
1005 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +01001006
1007 /*
1008 * If being flinged and user touches, stop the fling. isFinished
1009 * will be false if being flinged.
1010 */
1011 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001012 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +01001013 }
1014
1015 // Remember where the motion event started
1016 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001017 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01001018 mActivePointerId = ev.getPointerId(0);
1019 break;
1020 }
1021 case MotionEvent.ACTION_MOVE:
1022 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1023 if (activePointerIndex == -1) {
1024 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
1025 break;
1026 }
1027
1028 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02001029 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01001030 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001031 final int xDiff = Math.abs(x - mDownX);
1032 final int yDiff = Math.abs(deltaY);
1033 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01001034 setIsBeingDragged(true);
1035 if (deltaY > 0) {
1036 deltaY -= mTouchSlop;
1037 } else {
1038 deltaY += mTouchSlop;
1039 }
1040 }
1041 if (mIsBeingDragged) {
1042 // Scroll to follow the motion event
1043 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001044 int range = getScrollRange();
1045 if (mExpandedInThisMotion) {
1046 range = Math.min(range, mMaxScrollAfterExpand);
1047 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001048
1049 float scrollAmount;
1050 if (deltaY < 0) {
1051 scrollAmount = overScrollDown(deltaY);
1052 } else {
1053 scrollAmount = overScrollUp(deltaY, range);
1054 }
Selim Cinek67b22602014-03-10 15:40:16 +01001055
1056 // Calling overScrollBy will call onOverScrolled, which
1057 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001058 if (scrollAmount != 0.0f) {
1059 // The scrolling motion could not be compensated with the
1060 // existing overScroll, we have to scroll the view
1061 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
1062 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +01001063 }
Selim Cinek67b22602014-03-10 15:40:16 +01001064 }
1065 break;
1066 case MotionEvent.ACTION_UP:
1067 if (mIsBeingDragged) {
1068 final VelocityTracker velocityTracker = mVelocityTracker;
1069 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1070 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1071
Selim Cinek1408eb52014-06-02 14:45:38 +02001072 if (shouldOverScrollFling(initialVelocity)) {
1073 onOverScrollFling(true, initialVelocity);
1074 } else {
1075 if (getChildCount() > 0) {
1076 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
1077 float currentOverScrollTop = getCurrentOverScrollAmount(true);
1078 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
1079 fling(-initialVelocity);
1080 } else {
1081 onOverScrollFling(false, initialVelocity);
1082 }
1083 } else {
1084 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
1085 getScrollRange())) {
1086 postInvalidateOnAnimation();
1087 }
Selim Cinek67b22602014-03-10 15:40:16 +01001088 }
1089 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001090 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001091
1092 mActivePointerId = INVALID_POINTER;
1093 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +01001094 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001095
Selim Cinek67b22602014-03-10 15:40:16 +01001096 break;
1097 case MotionEvent.ACTION_CANCEL:
1098 if (mIsBeingDragged && getChildCount() > 0) {
1099 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1100 postInvalidateOnAnimation();
1101 }
1102 mActivePointerId = INVALID_POINTER;
1103 endDrag();
1104 }
1105 break;
1106 case MotionEvent.ACTION_POINTER_DOWN: {
1107 final int index = ev.getActionIndex();
1108 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +02001109 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +01001110 mActivePointerId = ev.getPointerId(index);
1111 break;
1112 }
1113 case MotionEvent.ACTION_POINTER_UP:
1114 onSecondaryPointerUp(ev);
1115 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +02001116 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +01001117 break;
1118 }
1119 return true;
1120 }
1121
Selim Cinek1408eb52014-06-02 14:45:38 +02001122 private void onOverScrollFling(boolean open, int initialVelocity) {
1123 if (mOverscrollTopChangedListener != null) {
1124 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
1125 }
1126 mDontReportNextOverScroll = true;
1127 setOverScrollAmount(0.0f, true, false);
1128 }
1129
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001130 /**
1131 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1132 *
1133 * @param deltaY The amount to scroll upwards, has to be positive.
1134 * @return The amount of scrolling to be performed by the scroller,
1135 * not handled by the overScroll amount.
1136 */
1137 private float overScrollUp(int deltaY, int range) {
1138 deltaY = Math.max(deltaY, 0);
1139 float currentTopAmount = getCurrentOverScrollAmount(true);
1140 float newTopAmount = currentTopAmount - deltaY;
1141 if (currentTopAmount > 0) {
1142 setOverScrollAmount(newTopAmount, true /* onTop */,
1143 false /* animate */);
1144 }
1145 // Top overScroll might not grab all scrolling motion,
1146 // we have to scroll as well.
1147 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1148 float newScrollY = mOwnScrollY + scrollAmount;
1149 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001150 if (!mExpandedInThisMotion) {
1151 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1152 // We overScroll on the top
1153 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1154 false /* onTop */,
1155 false /* animate */);
1156 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001157 mOwnScrollY = range;
1158 scrollAmount = 0.0f;
1159 }
1160 return scrollAmount;
1161 }
1162
1163 /**
1164 * Perform a scroll downward and adapt the overscroll amounts accordingly
1165 *
1166 * @param deltaY The amount to scroll downwards, has to be negative.
1167 * @return The amount of scrolling to be performed by the scroller,
1168 * not handled by the overScroll amount.
1169 */
1170 private float overScrollDown(int deltaY) {
1171 deltaY = Math.min(deltaY, 0);
1172 float currentBottomAmount = getCurrentOverScrollAmount(false);
1173 float newBottomAmount = currentBottomAmount + deltaY;
1174 if (currentBottomAmount > 0) {
1175 setOverScrollAmount(newBottomAmount, false /* onTop */,
1176 false /* animate */);
1177 }
1178 // Bottom overScroll might not grab all scrolling motion,
1179 // we have to scroll as well.
1180 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1181 float newScrollY = mOwnScrollY + scrollAmount;
1182 if (newScrollY < 0) {
1183 float currentTopPixels = getCurrentOverScrolledPixels(true);
1184 // We overScroll on the top
1185 setOverScrolledPixels(currentTopPixels - newScrollY,
1186 true /* onTop */,
1187 false /* animate */);
1188 mOwnScrollY = 0;
1189 scrollAmount = 0.0f;
1190 }
1191 return scrollAmount;
1192 }
1193
Selim Cinek67b22602014-03-10 15:40:16 +01001194 private void onSecondaryPointerUp(MotionEvent ev) {
1195 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1196 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1197 final int pointerId = ev.getPointerId(pointerIndex);
1198 if (pointerId == mActivePointerId) {
1199 // This was our active pointer going up. Choose a new
1200 // active pointer and adjust accordingly.
1201 // TODO: Make this decision more intelligent.
1202 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1203 mLastMotionY = (int) ev.getY(newPointerIndex);
1204 mActivePointerId = ev.getPointerId(newPointerIndex);
1205 if (mVelocityTracker != null) {
1206 mVelocityTracker.clear();
1207 }
1208 }
1209 }
1210
1211 private void initVelocityTrackerIfNotExists() {
1212 if (mVelocityTracker == null) {
1213 mVelocityTracker = VelocityTracker.obtain();
1214 }
1215 }
1216
1217 private void recycleVelocityTracker() {
1218 if (mVelocityTracker != null) {
1219 mVelocityTracker.recycle();
1220 mVelocityTracker = null;
1221 }
1222 }
1223
1224 private void initOrResetVelocityTracker() {
1225 if (mVelocityTracker == null) {
1226 mVelocityTracker = VelocityTracker.obtain();
1227 } else {
1228 mVelocityTracker.clear();
1229 }
1230 }
1231
1232 @Override
1233 public void computeScroll() {
1234 if (mScroller.computeScrollOffset()) {
1235 // This is called at drawing time by ViewGroup.
1236 int oldX = mScrollX;
1237 int oldY = mOwnScrollY;
1238 int x = mScroller.getCurrX();
1239 int y = mScroller.getCurrY();
1240
1241 if (oldX != x || oldY != y) {
1242 final int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001243 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1244 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001245 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001246 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1247 }
1248 }
Selim Cinek67b22602014-03-10 15:40:16 +01001249
1250 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001251 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001252 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001253 }
1254
1255 // Keep on drawing until the animation has finished.
1256 postInvalidateOnAnimation();
1257 }
1258 }
1259
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001260 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001261 protected boolean overScrollBy(int deltaX, int deltaY,
1262 int scrollX, int scrollY,
1263 int scrollRangeX, int scrollRangeY,
1264 int maxOverScrollX, int maxOverScrollY,
1265 boolean isTouchEvent) {
1266
1267 int newScrollY = scrollY + deltaY;
1268
1269 final int top = -maxOverScrollY;
1270 final int bottom = maxOverScrollY + scrollRangeY;
1271
1272 boolean clampedY = false;
1273 if (newScrollY > bottom) {
1274 newScrollY = bottom;
1275 clampedY = true;
1276 } else if (newScrollY < top) {
1277 newScrollY = top;
1278 clampedY = true;
1279 }
1280
1281 onOverScrolled(0, newScrollY, false, clampedY);
1282
1283 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001284 }
1285
1286 /**
1287 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1288 * overscroll effect based on numPixels. By default this will also cancel animations on the
1289 * same overScroll edge.
1290 *
1291 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1292 * the rubber-banding logic.
1293 * @param onTop Should the effect be applied on top of the scroller.
1294 * @param animate Should an animation be performed.
1295 */
1296 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001297 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001298 }
1299
1300 /**
1301 * Set the effective overScroll amount which will be directly reflected in the layout.
1302 * By default this will also cancel animations on the same overScroll edge.
1303 *
1304 * @param amount The amount to overScroll by.
1305 * @param onTop Should the effect be applied on top of the scroller.
1306 * @param animate Should an animation be performed.
1307 */
1308 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1309 setOverScrollAmount(amount, onTop, animate, true);
1310 }
1311
1312 /**
1313 * Set the effective overScroll amount which will be directly reflected in the layout.
1314 *
1315 * @param amount The amount to overScroll by.
1316 * @param onTop Should the effect be applied on top of the scroller.
1317 * @param animate Should an animation be performed.
1318 * @param cancelAnimators Should running animations be cancelled.
1319 */
1320 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1321 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001322 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1323 }
1324
1325 /**
1326 * Set the effective overScroll amount which will be directly reflected in the layout.
1327 *
1328 * @param amount The amount to overScroll by.
1329 * @param onTop Should the effect be applied on top of the scroller.
1330 * @param animate Should an animation be performed.
1331 * @param cancelAnimators Should running animations be cancelled.
1332 * @param isRubberbanded The value which will be passed to
1333 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1334 */
1335 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1336 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001337 if (cancelAnimators) {
1338 mStateAnimator.cancelOverScrollAnimators(onTop);
1339 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001340 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001341 }
1342
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001343 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1344 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001345 amount = Math.max(0, amount);
1346 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001347 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001348 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001349 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001350 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001351 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001352 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001353 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001354 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001355 }
1356 }
1357
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001358 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001359 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1360 if (mDontReportNextOverScroll) {
1361 mDontReportNextOverScroll = false;
1362 return;
1363 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001364 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001365 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001366 }
1367 }
1368
1369 public void setOverscrollTopChangedListener(
1370 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1371 mOverscrollTopChangedListener = overscrollTopChangedListener;
1372 }
1373
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001374 public float getCurrentOverScrollAmount(boolean top) {
1375 return mAmbientState.getOverScrollAmount(top);
1376 }
1377
1378 public float getCurrentOverScrolledPixels(boolean top) {
1379 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1380 }
1381
1382 private void setOverScrolledPixels(float amount, boolean onTop) {
1383 if (onTop) {
1384 mOverScrolledTopPixels = amount;
1385 } else {
1386 mOverScrolledBottomPixels = amount;
1387 }
1388 }
1389
Selim Cinek319bdc42014-05-01 23:01:58 +02001390 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001391 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001392 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001393 }
1394
1395 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001396 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001397 // Treat animating scrolls differently; see #computeScroll() for why.
1398 if (!mScroller.isFinished()) {
1399 final int oldX = mScrollX;
1400 final int oldY = mOwnScrollY;
1401 mScrollX = scrollX;
1402 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001403 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001404 springBack();
1405 } else {
1406 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1407 invalidateParentIfNeeded();
1408 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001409 float overScrollTop = getCurrentOverScrollAmount(true);
1410 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001411 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001412 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001413 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001414 }
Selim Cinek67b22602014-03-10 15:40:16 +01001415 }
Selim Cinek67b22602014-03-10 15:40:16 +01001416 } else {
1417 customScrollTo(scrollY);
1418 scrollTo(scrollX, mScrollY);
1419 }
1420 }
1421
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001422 private void springBack() {
1423 int scrollRange = getScrollRange();
1424 boolean overScrolledTop = mOwnScrollY <= 0;
1425 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1426 if (overScrolledTop || overScrolledBottom) {
1427 boolean onTop;
1428 float newAmount;
1429 if (overScrolledTop) {
1430 onTop = true;
1431 newAmount = -mOwnScrollY;
1432 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001433 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001434 } else {
1435 onTop = false;
1436 newAmount = mOwnScrollY - scrollRange;
1437 mOwnScrollY = scrollRange;
1438 }
1439 setOverScrollAmount(newAmount, onTop, false);
1440 setOverScrollAmount(0.0f, onTop, true);
1441 mScroller.forceFinished(true);
1442 }
1443 }
1444
Selim Cinek67b22602014-03-10 15:40:16 +01001445 private int getScrollRange() {
1446 int scrollRange = 0;
Jorim Jaggibe565df2014-04-28 17:51:23 +02001447 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001448 if (firstChild != null) {
Selim Cinek67b22602014-03-10 15:40:16 +01001449 int contentHeight = getContentHeight();
Selim Cineka5eaa602014-05-12 21:27:47 +02001450 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1451 + mBottomStackSlowDownHeight);
Selim Cinek4a1ac842014-05-01 15:51:58 +02001452 if (scrollRange > 0) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001453 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
Selim Cinek343e6e22014-04-11 21:23:30 +02001454 // We want to at least be able collapse the first item and not ending in a weird
1455 // end state.
Selim Cinek816c8e42015-11-19 12:00:45 -08001456 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight
1457 - firstChild.getMinHeight());
Selim Cinek343e6e22014-04-11 21:23:30 +02001458 }
Selim Cinek67b22602014-03-10 15:40:16 +01001459 }
1460 return scrollRange;
1461 }
1462
Selim Cinek343e6e22014-04-11 21:23:30 +02001463 /**
1464 * @return the first child which has visibility unequal to GONE
1465 */
Selim Cinekb55386d2015-12-16 17:26:49 -08001466 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02001467 int childCount = getChildCount();
1468 for (int i = 0; i < childCount; i++) {
1469 View child = getChildAt(i);
1470 if (child.getVisibility() != View.GONE) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001471 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02001472 }
1473 }
1474 return null;
1475 }
1476
Selim Cinek4a1ac842014-05-01 15:51:58 +02001477 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001478 * @return The first child which has visibility unequal to GONE which is currently below the
1479 * given translationY or equal to it.
1480 */
1481 private View getFirstChildBelowTranlsationY(float translationY) {
1482 int childCount = getChildCount();
1483 for (int i = 0; i < childCount; i++) {
1484 View child = getChildAt(i);
1485 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1486 return child;
1487 }
1488 }
1489 return null;
1490 }
1491
1492 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001493 * @return the last child which has visibility unequal to GONE
1494 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001495 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001496 int childCount = getChildCount();
1497 for (int i = childCount - 1; i >= 0; i--) {
1498 View child = getChildAt(i);
1499 if (child.getVisibility() != View.GONE) {
1500 return child;
1501 }
1502 }
1503 return null;
1504 }
1505
Jorim Jaggi069cd032014-05-15 03:09:01 +02001506 /**
1507 * @return the number of children which have visibility unequal to GONE
1508 */
1509 public int getNotGoneChildCount() {
1510 int childCount = getChildCount();
1511 int count = 0;
1512 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07001513 ExpandableView child = (ExpandableView) getChildAt(i);
1514 if (child.getVisibility() != View.GONE && !child.willBeGone()) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02001515 count++;
1516 }
1517 }
1518 return count;
1519 }
1520
Selim Cinek343e6e22014-04-11 21:23:30 +02001521 private int getMaxExpandHeight(View view) {
1522 if (view instanceof ExpandableNotificationRow) {
1523 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001524 return row.getIntrinsicHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001525 }
1526 return view.getHeight();
1527 }
1528
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001529 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001530 return mContentHeight;
1531 }
1532
1533 private void updateContentHeight() {
1534 int height = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08001535 float previousIncreasedAmount = 0.0f;
Selim Cinek67b22602014-03-10 15:40:16 +01001536 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08001537 ExpandableView expandableView = (ExpandableView) getChildAt(i);
1538 if (expandableView.getVisibility() != View.GONE) {
Selim Cinek42357e02016-02-24 18:48:01 -08001539 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001540 if (height != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08001541 height += (int) NotificationUtils.interpolate(
1542 mPaddingBetweenElements,
1543 mIncreasedPaddingBetweenElements,
1544 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Jorim Jaggid4a57442014-04-10 02:45:55 +02001545 }
Selim Cinek42357e02016-02-24 18:48:01 -08001546 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08001547 height += expandableView.getIntrinsicHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001548 }
1549 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001550 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001551 }
1552
Selim Cinek6811d722016-01-19 17:53:12 -08001553 private void updateBackground() {
1554 if (mAmbientState.isDark()) {
1555 return;
1556 }
1557 updateBackgroundBounds();
1558 if (!mCurrentBounds.equals(mBackgroundBounds)) {
Selim Cinek614576e2016-01-20 10:54:09 -08001559 if (mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom || areBoundsAnimating()) {
1560 startBackgroundAnimation();
1561 } else {
1562 mCurrentBounds.set(mBackgroundBounds);
1563 applyCurrentBackgroundBounds();
1564 }
1565 } else {
1566 if (mBottomAnimator != null) {
1567 mBottomAnimator.cancel();
1568 }
1569 if (mTopAnimator != null) {
1570 mTopAnimator.cancel();
1571 }
Selim Cinek6811d722016-01-19 17:53:12 -08001572 }
Selim Cinek614576e2016-01-20 10:54:09 -08001573 mAnimateNextBackgroundBottom = false;
1574 mAnimateNextBackgroundTop = false;
1575 }
1576
1577 private boolean areBoundsAnimating() {
1578 return mBottomAnimator != null || mTopAnimator != null;
1579 }
1580
1581 private void startBackgroundAnimation() {
1582 startBottomAnimation();
1583 startTopAnimation();
1584 }
1585
1586 private void startTopAnimation() {
1587 int previousEndValue = mEndAnimationRect.top;
1588 int newEndValue = mBackgroundBounds.top;
1589 ObjectAnimator previousAnimator = mTopAnimator;
1590 if (previousAnimator != null && previousEndValue == newEndValue) {
1591 return;
1592 }
1593 if (!mAnimateNextBackgroundTop) {
1594 // just a local update was performed
1595 if (previousAnimator != null) {
1596 // we need to increase all animation keyframes of the previous animator by the
1597 // relative change to the end value
1598 int previousStartValue = mStartAnimationRect.top;
1599 PropertyValuesHolder[] values = previousAnimator.getValues();
1600 values[0].setIntValues(previousStartValue, newEndValue);
1601 mStartAnimationRect.top = previousStartValue;
1602 mEndAnimationRect.top = newEndValue;
1603 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1604 return;
1605 } else {
1606 // no new animation needed, let's just apply the value
1607 setBackgroundTop(newEndValue);
1608 return;
1609 }
1610 }
1611 if (previousAnimator != null) {
1612 previousAnimator.cancel();
1613 }
1614 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
1615 mCurrentBounds.top, newEndValue);
1616 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1617 animator.setInterpolator(interpolator);
1618 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1619 // remove the tag when the animation is finished
1620 animator.addListener(new AnimatorListenerAdapter() {
1621 @Override
1622 public void onAnimationEnd(Animator animation) {
1623 mStartAnimationRect.top = -1;
1624 mEndAnimationRect.top = -1;
1625 mTopAnimator = null;
1626 }
1627 });
1628 animator.start();
1629 mStartAnimationRect.top = mCurrentBounds.top;
1630 mEndAnimationRect.top = newEndValue;
1631 mTopAnimator = animator;
1632 }
1633
1634 private void startBottomAnimation() {
1635 int previousStartValue = mStartAnimationRect.bottom;
1636 int previousEndValue = mEndAnimationRect.bottom;
1637 int newEndValue = mBackgroundBounds.bottom;
1638 ObjectAnimator previousAnimator = mBottomAnimator;
1639 if (previousAnimator != null && previousEndValue == newEndValue) {
1640 return;
1641 }
1642 if (!mAnimateNextBackgroundBottom) {
1643 // just a local update was performed
1644 if (previousAnimator != null) {
1645 // we need to increase all animation keyframes of the previous animator by the
1646 // relative change to the end value
1647 PropertyValuesHolder[] values = previousAnimator.getValues();
1648 values[0].setIntValues(previousStartValue, newEndValue);
1649 mStartAnimationRect.bottom = previousStartValue;
1650 mEndAnimationRect.bottom = newEndValue;
1651 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1652 return;
1653 } else {
1654 // no new animation needed, let's just apply the value
1655 setBackgroundBottom(newEndValue);
1656 return;
1657 }
1658 }
1659 if (previousAnimator != null) {
1660 previousAnimator.cancel();
1661 }
1662 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
1663 mCurrentBounds.bottom, newEndValue);
1664 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1665 animator.setInterpolator(interpolator);
1666 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1667 // remove the tag when the animation is finished
1668 animator.addListener(new AnimatorListenerAdapter() {
1669 @Override
1670 public void onAnimationEnd(Animator animation) {
1671 mStartAnimationRect.bottom = -1;
1672 mEndAnimationRect.bottom = -1;
1673 mBottomAnimator = null;
1674 }
1675 });
1676 animator.start();
1677 mStartAnimationRect.bottom = mCurrentBounds.bottom;
1678 mEndAnimationRect.bottom = newEndValue;
1679 mBottomAnimator = animator;
1680 }
1681
1682 private void setBackgroundTop(int top) {
1683 mCurrentBounds.top = top;
1684 applyCurrentBackgroundBounds();
1685 }
1686
1687 public void setBackgroundBottom(int bottom) {
1688 mCurrentBounds.bottom = bottom;
1689 applyCurrentBackgroundBounds();
1690 }
1691
1692 private void applyCurrentBackgroundBounds() {
1693 mScrimController.setExcludedBackgroundArea(mCurrentBounds);
Selim Cinek614576e2016-01-20 10:54:09 -08001694 invalidate();
Selim Cinek6811d722016-01-19 17:53:12 -08001695 }
1696
1697 /**
1698 * Update the background bounds to the new desired bounds
1699 */
1700 private void updateBackgroundBounds() {
Selim Cinek614576e2016-01-20 10:54:09 -08001701 mBackgroundBounds.left = (int) getX();
1702 mBackgroundBounds.right = (int) (getX() + getWidth());
1703 if (!mIsExpanded) {
1704 mBackgroundBounds.top = 0;
1705 mBackgroundBounds.bottom = 0;
1706 }
1707 ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001708 int top = 0;
1709 if (firstView != null) {
1710 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView);
Selim Cinek614576e2016-01-20 10:54:09 -08001711 if (mAnimateNextBackgroundTop
1712 || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
1713 || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
Selim Cinek6811d722016-01-19 17:53:12 -08001714 // we're ending up at the same location as we are now, lets just skip the animation
1715 top = finalTranslationY;
1716 } else {
1717 top = (int) firstView.getTranslationY();
1718 }
1719 }
Selim Cinek614576e2016-01-20 10:54:09 -08001720 ActivatableNotificationView lastView = mLastVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001721 int bottom = 0;
1722 if (lastView != null) {
1723 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView);
1724 int finalHeight = StackStateAnimator.getFinalActualHeight(lastView);
1725 int finalBottom = finalTranslationY + finalHeight;
1726 finalBottom = Math.min(finalBottom, getHeight());
Selim Cinek614576e2016-01-20 10:54:09 -08001727 if (mAnimateNextBackgroundBottom
1728 || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
1729 || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
Selim Cinek6811d722016-01-19 17:53:12 -08001730 // we're ending up at the same location as we are now, lets just skip the animation
1731 bottom = finalBottom;
1732 } else {
1733 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
1734 bottom = Math.min(bottom, getHeight());
1735 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001736 } else {
1737 top = (int) (mTopPadding + mStackTranslation);
Selim Cinek7db42982016-02-02 15:21:41 -08001738 bottom = top;
Selim Cinek6811d722016-01-19 17:53:12 -08001739 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001740 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1741 mBackgroundBounds.top = (int) Math.max(mTopPadding + mStackTranslation, top);
1742 } else {
1743 // otherwise the animation from the shade to the keyguard will jump as it's maxed
1744 mBackgroundBounds.top = Math.max(0, top);
1745 }
1746 mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
Selim Cinek6811d722016-01-19 17:53:12 -08001747 }
1748
Selim Cinek614576e2016-01-20 10:54:09 -08001749 private ActivatableNotificationView getFirstPinnedHeadsUp() {
1750 int childCount = getChildCount();
1751 for (int i = 0; i < childCount; i++) {
1752 View child = getChildAt(i);
1753 if (child.getVisibility() != View.GONE
1754 && child instanceof ExpandableNotificationRow) {
1755 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1756 if (row.isPinned()) {
1757 return row;
1758 }
1759 }
1760 }
1761 return null;
1762 }
1763
1764 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001765 int childCount = getChildCount();
1766 for (int i = childCount - 1; i >= 0; i--) {
1767 View child = getChildAt(i);
1768 if (child.getVisibility() != View.GONE
1769 && child instanceof ActivatableNotificationView) {
1770 return (ActivatableNotificationView) child;
1771 }
1772 }
1773 return null;
1774 }
1775
Selim Cinek614576e2016-01-20 10:54:09 -08001776 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001777 int childCount = getChildCount();
1778 for (int i = 0; i < childCount; i++) {
1779 View child = getChildAt(i);
1780 if (child.getVisibility() != View.GONE
1781 && child instanceof ActivatableNotificationView) {
1782 return (ActivatableNotificationView) child;
1783 }
1784 }
1785 return null;
1786 }
1787
Selim Cinek67b22602014-03-10 15:40:16 +01001788 /**
1789 * Fling the scroll view
1790 *
1791 * @param velocityY The initial velocity in the Y direction. Positive
1792 * numbers mean that the finger/cursor is moving down the screen,
1793 * which means we want to scroll towards the top.
1794 */
1795 private void fling(int velocityY) {
1796 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001797 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001798
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001799 float topAmount = getCurrentOverScrollAmount(true);
1800 float bottomAmount = getCurrentOverScrollAmount(false);
1801 if (velocityY < 0 && topAmount > 0) {
1802 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001803 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001804 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001805 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001806 * mOverflingDistance + topAmount;
1807 } else if (velocityY > 0 && bottomAmount > 0) {
1808 mOwnScrollY += bottomAmount;
1809 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001810 mMaxOverScroll = Math.abs(velocityY) / 1000f
1811 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1812 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001813 } else {
1814 // it will be set once we reach the boundary
1815 mMaxOverScroll = 0.0f;
1816 }
Selim Cinek94ab18c2016-02-25 12:35:51 -08001817 int minScrollY = Math.max(0, scrollRange);
1818 if (mExpandedInThisMotion) {
1819 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
1820 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001821 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek94ab18c2016-02-25 12:35:51 -08001822 minScrollY, 0, mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01001823
1824 postInvalidateOnAnimation();
1825 }
1826 }
1827
Selim Cinek1408eb52014-06-02 14:45:38 +02001828 /**
1829 * @return Whether a fling performed on the top overscroll edge lead to the expanded
1830 * overScroll view (i.e QS).
1831 */
1832 private boolean shouldOverScrollFling(int initialVelocity) {
1833 float topOverScroll = getCurrentOverScrollAmount(true);
1834 return mScrolledToTopOnFirstDown
1835 && !mExpandedInThisMotion
1836 && topOverScroll > mMinTopOverScrollToEscape
1837 && initialVelocity > 0;
1838 }
1839
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001840 /**
1841 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1842 * account.
1843 *
1844 * @param qsHeight the top padding imposed by the quick settings panel
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001845 * @param animate whether to animate the change
1846 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1847 * {@code qsHeight} is the final top padding
1848 */
Jason Monk16ac3772016-02-10 15:39:21 -05001849 public void updateTopPadding(float qsHeight, boolean animate,
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001850 boolean ignoreIntrinsicPadding) {
Jason Monk16ac3772016-02-10 15:39:21 -05001851 float start = qsHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001852 float stackHeight = getHeight() - start;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001853 int minStackHeight = getMinStackHeight();
1854 if (stackHeight <= minStackHeight) {
1855 float overflow = minStackHeight - stackHeight;
1856 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001857 start = getHeight() - stackHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001858 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02001859 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001860 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001861 }
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001862 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1863 animate);
1864 setStackHeight(mLastSetStackHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02001865 }
1866
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001867 public int getMinStackHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001868 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek31aada42015-12-18 17:51:15 -08001869 final int firstChildMinHeight = firstChild != null ? firstChild.getMinHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08001870 : mCollapsedSize;
1871 return firstChildMinHeight + mBottomStackPeekSize + mCollapseSecondCardPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001872 }
1873
1874 public float getTopPaddingOverflow() {
1875 return mTopPaddingOverflow;
1876 }
1877
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001878 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001879 final ExpandableView firstChild = getFirstChildNotGone();
1880 final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
1881 : mCollapsedSize;
1882 return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001883 + mCollapseSecondCardPadding;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001884 }
1885
Selim Cinek1408eb52014-06-02 14:45:38 +02001886 private int clampPadding(int desiredPadding) {
1887 return Math.max(desiredPadding, mIntrinsicPadding);
1888 }
1889
Selim Cinekfed1ab62014-06-17 14:10:33 -07001890 private float getRubberBandFactor(boolean onTop) {
1891 if (!onTop) {
1892 return RUBBER_BAND_FACTOR_NORMAL;
1893 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001894 if (mExpandedInThisMotion) {
1895 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001896 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001897 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1898 } else if (mScrolledToTopOnFirstDown) {
1899 return 1.0f;
1900 }
1901 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02001902 }
1903
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001904 /**
1905 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1906 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1907 * overscroll view (e.g. expand QS).
1908 */
1909 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001910 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001911 || !mScrolledToTopOnFirstDown;
1912 }
1913
Selim Cinek67b22602014-03-10 15:40:16 +01001914 private void endDrag() {
1915 setIsBeingDragged(false);
1916
1917 recycleVelocityTracker();
1918
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001919 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1920 setOverScrollAmount(0, true /* onTop */, true /* animate */);
1921 }
1922 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1923 setOverScrollAmount(0, false /* onTop */, true /* animate */);
1924 }
Selim Cinek67b22602014-03-10 15:40:16 +01001925 }
1926
Jorim Jaggi56306252014-07-03 00:40:09 +02001927 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1928 ev.offsetLocation(sourceView.getX(), sourceView.getY());
1929 ev.offsetLocation(-targetView.getX(), -targetView.getY());
1930 }
1931
Selim Cinek67b22602014-03-10 15:40:16 +01001932 @Override
1933 public boolean onInterceptTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001934 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001935 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001936 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001937 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001938 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1939 }
Selim Cinek67b22602014-03-10 15:40:16 +01001940 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001941 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01001942 scrollWantsIt = onInterceptTouchEventScroll(ev);
1943 }
1944 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001945 if (!mIsBeingDragged
1946 && !mExpandingNotification
1947 && !mExpandedInThisMotion
1948 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001949 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1950 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001951 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1952 }
1953
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001954 private void handleEmptySpaceClick(MotionEvent ev) {
1955 switch (ev.getActionMasked()) {
1956 case MotionEvent.ACTION_MOVE:
1957 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1958 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1959 mTouchIsClick = false;
1960 }
1961 break;
1962 case MotionEvent.ACTION_UP:
1963 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1964 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1965 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1966 }
1967 break;
1968 }
1969 }
1970
Selim Cinek1408eb52014-06-02 14:45:38 +02001971 private void initDownStates(MotionEvent ev) {
1972 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1973 mExpandedInThisMotion = false;
1974 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001975 mDisallowScrollingInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001976 mTouchIsClick = true;
1977 mInitialTouchX = ev.getX();
1978 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001979 }
Selim Cinek67b22602014-03-10 15:40:16 +01001980 }
1981
Selim Cinekef5127e2015-12-21 16:55:58 -08001982 public void setChildTransferInProgress(boolean childTransferInProgress) {
1983 mChildTransferInProgress = childTransferInProgress;
1984 }
1985
Christoph Studer068f5922014-04-08 17:43:07 -04001986 @Override
Adam Powell6690d012015-06-17 16:41:56 -07001987 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04001988 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01001989 // we only call our internal methods if this is actually a removal and not just a
1990 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08001991 if (!mChildTransferInProgress) {
Selim Cinekb5605e52015-02-20 18:21:41 +01001992 onViewRemovedInternal(child);
1993 }
1994 }
1995
1996 private void onViewRemovedInternal(View child) {
Selim Cinekb55386d2015-12-16 17:26:49 -08001997 mStackScrollAlgorithm.notifyChildrenChanged(this);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001998 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001999 // This is only a position change, don't do anything special
2000 return;
2001 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02002002 ((ExpandableView) child).setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04002003 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinek61633a82016-01-25 15:54:10 -08002004 updateScrollStateForRemovedChild((ExpandableView) child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07002005 boolean animationGenerated = generateRemoveAnimation(child);
2006 if (animationGenerated && !mSwipedOutViews.contains(child)) {
2007 // Add this view to an overlay in order to ensure that it will still be temporary
2008 // drawn when removed
2009 getOverlay().add(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002010 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002011 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02002012
2013 // Make sure the clipRect we might have set is removed
Selim Cineka272dfe2015-02-20 18:12:28 +01002014 ((ExpandableView) child).setClipTopOptimization(0);
Selim Cinekc27437b2014-05-14 10:23:33 +02002015 }
2016
Selim Cinekb5605e52015-02-20 18:21:41 +01002017 private boolean isChildInGroup(View child) {
2018 return child instanceof ExpandableNotificationRow
2019 && mGroupManager.isChildInGroupWithSummary(
2020 ((ExpandableNotificationRow) child).getStatusBarNotification());
2021 }
2022
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002023 /**
2024 * Generate a remove animation for a child view.
2025 *
2026 * @param child The view to generate the remove animation for.
2027 * @return Whether an animation was generated.
2028 */
2029 private boolean generateRemoveAnimation(View child) {
Selim Cineke0890e52015-06-17 11:17:08 -07002030 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002031 mAddedHeadsUpChildren.remove(child);
2032 return false;
2033 }
Selim Cinek0fccc722015-07-29 17:04:36 -07002034 if (isClickedHeadsUp(child)) {
2035 // An animation is already running, add it to the Overlay
2036 mClearOverlayViewsWhenFinished.add(child);
2037 return true;
2038 }
Selim Cinekb5605e52015-02-20 18:21:41 +01002039 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002040 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02002041 // Generate Animations
2042 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002043 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002044 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02002045 } else {
2046 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002047 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002048 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02002049 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002050 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002051 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002052 }
2053
Selim Cinek0fccc722015-07-29 17:04:36 -07002054 private boolean isClickedHeadsUp(View child) {
2055 return HeadsUpManager.isClickedHeadsUpNotification(child);
2056 }
2057
Selim Cineke0890e52015-06-17 11:17:08 -07002058 /**
2059 * Remove a removed child view from the heads up animations if it was just added there
2060 *
2061 * @return whether any child was removed from the list to animate
2062 */
2063 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2064 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002065 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2066 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07002067 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002068 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07002069 mTmpList.add(eventPair);
2070 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002071 }
2072 }
Selim Cineke0890e52015-06-17 11:17:08 -07002073 if (hasAddEvent) {
2074 // This child was just added lets remove all events.
2075 mHeadsUpChangeAnimations.removeAll(mTmpList);
2076 }
2077 mTmpList.clear();
2078 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002079 }
2080
Selim Cinek572bbd42014-04-25 16:43:27 +02002081 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01002082 * @param child the child to query
2083 * @return whether a view is not a top level child but a child notification and that group is
2084 * not expanded
2085 */
2086 private boolean isChildInInvisibleGroup(View child) {
2087 if (child instanceof ExpandableNotificationRow) {
2088 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2089 ExpandableNotificationRow groupSummary =
2090 mGroupManager.getGroupSummary(row.getStatusBarNotification());
2091 if (groupSummary != null && groupSummary != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07002092 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01002093 }
2094 }
2095 return false;
2096 }
2097
2098 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02002099 * Updates the scroll position when a child was removed
2100 *
2101 * @param removedChild the removed child
2102 */
Selim Cinek61633a82016-01-25 15:54:10 -08002103 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002104 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinek42357e02016-02-24 18:48:01 -08002105 int padding = (int) NotificationUtils.interpolate(
2106 mPaddingBetweenElements,
2107 mIncreasedPaddingBetweenElements,
2108 removedChild.getIncreasedPaddingAmount());
Selim Cinek61633a82016-01-25 15:54:10 -08002109 int childHeight = getIntrinsicHeight(removedChild) + padding;
Selim Cinek572bbd42014-04-25 16:43:27 +02002110 int endPosition = startingPosition + childHeight;
2111 if (endPosition <= mOwnScrollY) {
2112 // This child is fully scrolled of the top, so we have to deduct its height from the
2113 // scrollPosition
2114 mOwnScrollY -= childHeight;
2115 } else if (startingPosition < mOwnScrollY) {
2116 // This child is currently being scrolled into, set the scroll position to the start of
2117 // this child
2118 mOwnScrollY = startingPosition;
2119 }
2120 }
2121
Selim Cinekd7c4e002014-07-04 18:36:42 +02002122 private int getIntrinsicHeight(View view) {
2123 if (view instanceof ExpandableView) {
2124 ExpandableView expandableView = (ExpandableView) view;
2125 return expandableView.getIntrinsicHeight();
2126 }
2127 return view.getHeight();
2128 }
2129
Selim Cinek572bbd42014-04-25 16:43:27 +02002130 private int getPositionInLinearLayout(View requestedChild) {
2131 int position = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08002132 float previousIncreasedAmount = 0.0f;
Selim Cinek572bbd42014-04-25 16:43:27 +02002133 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002134 ExpandableView child = (ExpandableView) getChildAt(i);
2135 boolean notGone = child.getVisibility() != View.GONE;
2136 if (notGone) {
Selim Cinek42357e02016-02-24 18:48:01 -08002137 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
Selim Cinek61633a82016-01-25 15:54:10 -08002138 if (position != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08002139 position += (int) NotificationUtils.interpolate(
2140 mPaddingBetweenElements,
2141 mIncreasedPaddingBetweenElements,
2142 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Selim Cinek61633a82016-01-25 15:54:10 -08002143 }
Selim Cinek42357e02016-02-24 18:48:01 -08002144 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08002145 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002146 if (child == requestedChild) {
2147 return position;
2148 }
Selim Cinek61633a82016-01-25 15:54:10 -08002149 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02002150 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002151 }
2152 }
2153 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02002154 }
2155
2156 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002157 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02002158 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002159 onViewAddedInternal(child);
2160 }
2161
Selim Cinek614576e2016-01-20 10:54:09 -08002162 private void updateFirstAndLastBackgroundViews() {
2163 ActivatableNotificationView firstChild = getFirstChildWithBackground();
2164 ActivatableNotificationView lastChild = getLastChildWithBackground();
2165 if (mAnimationsEnabled && mIsExpanded) {
2166 mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
2167 mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
2168 } else {
2169 mAnimateNextBackgroundTop = false;
2170 mAnimateNextBackgroundBottom = false;
2171 }
2172 mFirstVisibleBackgroundChild = firstChild;
2173 mLastVisibleBackgroundChild = lastChild;
2174 }
2175
Selim Cinekb5605e52015-02-20 18:21:41 +01002176 private void onViewAddedInternal(View child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002177 updateHideSensitiveForChild(child);
Selim Cinekb55386d2015-12-16 17:26:49 -08002178 mStackScrollAlgorithm.notifyChildrenChanged(this);
Jorim Jaggibe565df2014-04-28 17:51:23 +02002179 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00002180 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002181 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02002182 updateChronometerForChild(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002183 }
2184
Selim Cinekd06c41c2015-07-06 14:51:36 -07002185 private void updateHideSensitiveForChild(View child) {
2186 if (mAmbientState.isHideSensitive() && child instanceof ExpandableView) {
2187 ExpandableView expandableView = (ExpandableView) child;
2188 expandableView.setHideSensitiveForIntrinsicHeight(true);
2189 }
2190 }
2191
Selim Cinekb5605e52015-02-20 18:21:41 +01002192 public void notifyGroupChildRemoved(View row) {
2193 onViewRemovedInternal(row);
2194 }
2195
2196 public void notifyGroupChildAdded(View row) {
2197 onViewAddedInternal(row);
2198 }
2199
Jorim Jaggi75c95042014-05-16 19:09:59 +02002200 public void setAnimationsEnabled(boolean animationsEnabled) {
2201 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002202 updateNotificationAnimationStates();
2203 }
2204
2205 private void updateNotificationAnimationStates() {
Selim Cinek8d490d42015-04-10 00:05:50 -07002206 boolean running = mAnimationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002207 int childCount = getChildCount();
2208 for (int i = 0; i < childCount; i++) {
2209 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07002210 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02002211 updateAnimationState(running, child);
2212 }
2213 }
2214
Selim Cinek51ae05d2014-09-09 15:51:38 +02002215 private void updateAnimationState(View child) {
Selim Cinek01af3342016-02-09 19:25:31 -08002216 updateAnimationState(mAnimationsEnabled && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002217 }
2218
2219
Selim Cinekcab4a602014-09-03 14:47:57 +02002220 private void updateAnimationState(boolean running, View child) {
2221 if (child instanceof ExpandableNotificationRow) {
2222 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2223 row.setIconAnimationRunning(running);
2224 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02002225 }
2226
2227 public boolean isAddOrRemoveAnimationPending() {
2228 return mNeedsAnimation
2229 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
2230 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002231 /**
2232 * Generate an animation for an added child view.
2233 *
2234 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00002235 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002236 */
Jorim Jaggif6411742014-08-05 17:10:43 +00002237 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002238 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002239 // Generate Animations
2240 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00002241 if (fromMoreCard) {
2242 mFromMoreCardAdditions.add(child);
2243 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002244 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02002245 }
Adrian Roos777ef562015-12-01 17:37:14 -08002246 if (isHeadsUp(child) && !mChangePositionInProgress) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002247 mAddedHeadsUpChildren.add(child);
2248 mChildrenToAddAnimated.remove(child);
2249 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002250 }
2251
2252 /**
2253 * Change the position of child to a new location
2254 *
2255 * @param child the view to change the position for
2256 * @param newIndex the new index
2257 */
2258 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002259 int currentIndex = indexOfChild(child);
2260 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002261 mChangePositionInProgress = true;
Selim Cinekc27437b2014-05-14 10:23:33 +02002262 removeView(child);
2263 addView(child, newIndex);
Selim Cinek159ffdb2014-06-04 22:24:18 +02002264 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04002265 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002266 mChildrenChangingPositions.add(child);
2267 mNeedsAnimation = true;
2268 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002269 }
2270 }
2271
Selim Cinekf4c19962014-05-01 21:55:31 +02002272 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002273 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002274 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002275 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002276 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002277 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002278 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
2279 mGoToFullShadeDelay);
Selim Cinek614576e2016-01-20 10:54:09 -08002280 setAnimationRunning(true);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002281 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08002282 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08002283 updateViewShadows();
Selim Cinekf4c19962014-05-01 21:55:31 +02002284 } else {
2285 applyCurrentState();
2286 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002287 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02002288 }
2289
2290 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07002291 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02002292 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002293 generateChildAdditionEvents();
2294 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02002295 generateSnapBackEvents();
2296 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002297 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002298 generateActivateEvent();
2299 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02002300 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04002301 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002302 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02002303 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01002304 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02002305 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002306 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002307 }
2308
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002309 private void generateHeadsUpAnimationEvents() {
2310 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002311 ExpandableNotificationRow row = eventPair.first;
2312 boolean isHeadsUp = eventPair.second;
2313 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
2314 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07002315 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07002316 if (!mIsExpanded && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07002317 type = row.wasJustClicked()
2318 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
2319 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinekeaee9c02015-06-25 11:04:20 -04002320 } else {
2321 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
2322 if (viewState == null) {
2323 // A view state was never generated for this view, so we don't need to animate
2324 // this. This may happen with notification children.
2325 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07002326 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04002327 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
2328 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
2329 // Our custom add animation
2330 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
2331 } else {
2332 // Normal add animation
2333 type = AnimationEvent.ANIMATION_TYPE_ADD;
2334 }
2335 onBottom = !pinnedAndClosed;
2336 }
Selim Cineka59ecc32015-04-07 10:51:49 -07002337 }
2338 AnimationEvent event = new AnimationEvent(row, type);
2339 event.headsUpFromBottom = onBottom;
2340 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002341 }
2342 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07002343 mAddedHeadsUpChildren.clear();
2344 }
2345
Selim Cinekeaee9c02015-06-25 11:04:20 -04002346 private boolean shouldHunAppearFromBottom(StackViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002347 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
2348 return false;
2349 }
2350 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002351 }
2352
Selim Cinekb5605e52015-02-20 18:21:41 +01002353 private void generateGroupExpansionEvent() {
2354 // Generate a group expansion/collapsing event if there is such a group at all
2355 if (mExpandedGroupView != null) {
2356 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
2357 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
2358 mExpandedGroupView = null;
2359 }
2360 }
2361
Selim Cineka5e211b2014-08-11 17:35:48 +02002362 private void generateViewResizeEvent() {
2363 if (mNeedViewResizeAnimation) {
2364 mAnimationEvents.add(
2365 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
2366 }
2367 mNeedViewResizeAnimation = false;
2368 }
2369
Selim Cinekeb973562014-05-02 17:07:49 +02002370 private void generateSnapBackEvents() {
2371 for (View child : mSnappedBackChildren) {
2372 mAnimationEvents.add(new AnimationEvent(child,
2373 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
2374 }
2375 mSnappedBackChildren.clear();
2376 }
2377
2378 private void generateDragEvents() {
2379 for (View child : mDragAnimPendingChildren) {
2380 mAnimationEvents.add(new AnimationEvent(child,
2381 AnimationEvent.ANIMATION_TYPE_START_DRAG));
2382 }
2383 mDragAnimPendingChildren.clear();
2384 }
2385
Selim Cinek572bbd42014-04-25 16:43:27 +02002386 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002387 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002388 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
2389 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002390 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
2391 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002392 AnimationEvent event = new AnimationEvent(child, animationType);
2393
2394 // we need to know the view after this one
2395 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
2396 mAnimationEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +02002397 }
2398 mSwipedOutViews.clear();
2399 mChildrenToRemoveAnimated.clear();
2400 }
2401
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002402 private void generatePositionChangeEvents() {
2403 for (View child : mChildrenChangingPositions) {
2404 mAnimationEvents.add(new AnimationEvent(child,
2405 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2406 }
2407 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01002408 if (mGenerateChildOrderChangedEvent) {
2409 mAnimationEvents.add(new AnimationEvent(null,
2410 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2411 mGenerateChildOrderChangedEvent = false;
2412 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002413 }
2414
Selim Cinek572bbd42014-04-25 16:43:27 +02002415 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002416 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002417 if (mFromMoreCardAdditions.contains(child)) {
2418 mAnimationEvents.add(new AnimationEvent(child,
2419 AnimationEvent.ANIMATION_TYPE_ADD,
2420 StackStateAnimator.ANIMATION_DURATION_STANDARD));
2421 } else {
2422 mAnimationEvents.add(new AnimationEvent(child,
2423 AnimationEvent.ANIMATION_TYPE_ADD));
2424 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002425 }
2426 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002427 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04002428 }
2429
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002430 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02002431 if (mTopPaddingNeedsAnimation) {
2432 mAnimationEvents.add(
2433 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2434 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002435 mTopPaddingNeedsAnimation = false;
2436 }
2437
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002438 private void generateActivateEvent() {
2439 if (mActivateNeedsAnimation) {
2440 mAnimationEvents.add(
2441 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2442 }
2443 mActivateNeedsAnimation = false;
2444 }
2445
Selim Cinekd9acca52014-09-01 22:33:25 +02002446 private void generateAnimateEverythingEvent() {
2447 if (mEverythingNeedsAnimation) {
2448 mAnimationEvents.add(
2449 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2450 }
2451 mEverythingNeedsAnimation = false;
2452 }
2453
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002454 private void generateDimmedEvent() {
2455 if (mDimmedNeedsAnimation) {
2456 mAnimationEvents.add(
2457 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2458 }
2459 mDimmedNeedsAnimation = false;
2460 }
2461
Jorim Jaggiae441282014-08-01 02:45:18 +02002462 private void generateHideSensitiveEvent() {
2463 if (mHideSensitiveNeedsAnimation) {
2464 mAnimationEvents.add(
2465 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2466 }
2467 mHideSensitiveNeedsAnimation = false;
2468 }
2469
John Spurlockbf370992014-06-17 13:58:31 -04002470 private void generateDarkEvent() {
2471 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002472 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2473 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2474 mAnimationEvents.add(ev);
John Spurlockbf370992014-06-17 13:58:31 -04002475 }
2476 mDarkNeedsAnimation = false;
2477 }
2478
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002479 private void generateGoToFullShadeEvent() {
2480 if (mGoToFullShadeNeedsAnimation) {
2481 mAnimationEvents.add(
2482 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2483 }
2484 mGoToFullShadeNeedsAnimation = false;
2485 }
2486
Selim Cinek67b22602014-03-10 15:40:16 +01002487 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002488 if (!isScrollingEnabled()) {
2489 return false;
2490 }
Selim Cinek67b22602014-03-10 15:40:16 +01002491 /*
2492 * This method JUST determines whether we want to intercept the motion.
2493 * If we return true, onMotionEvent will be called and we do the actual
2494 * scrolling there.
2495 */
2496
2497 /*
2498 * Shortcut the most recurring case: the user is in the dragging
Chris Wren5d53df42015-06-26 11:26:03 -04002499 * state and is moving their finger. We want to intercept this
Selim Cinek67b22602014-03-10 15:40:16 +01002500 * motion.
2501 */
2502 final int action = ev.getAction();
2503 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2504 return true;
2505 }
2506
Selim Cinek67b22602014-03-10 15:40:16 +01002507 switch (action & MotionEvent.ACTION_MASK) {
2508 case MotionEvent.ACTION_MOVE: {
2509 /*
2510 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04002511 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01002512 */
2513
2514 /*
2515 * Locally do absolute value. mLastMotionY is set to the y value
2516 * of the down event.
2517 */
2518 final int activePointerId = mActivePointerId;
2519 if (activePointerId == INVALID_POINTER) {
2520 // If we don't have a valid id, the touch down wasn't on content.
2521 break;
2522 }
2523
2524 final int pointerIndex = ev.findPointerIndex(activePointerId);
2525 if (pointerIndex == -1) {
2526 Log.e(TAG, "Invalid pointerId=" + activePointerId
2527 + " in onInterceptTouchEvent");
2528 break;
2529 }
2530
2531 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02002532 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01002533 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02002534 final int xDiff = Math.abs(x - mDownX);
2535 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01002536 setIsBeingDragged(true);
2537 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002538 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01002539 initVelocityTrackerIfNotExists();
2540 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01002541 }
2542 break;
2543 }
2544
2545 case MotionEvent.ACTION_DOWN: {
2546 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05302547 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01002548 if (getChildAtPosition(ev.getX(), y) == null) {
2549 setIsBeingDragged(false);
2550 recycleVelocityTracker();
2551 break;
2552 }
2553
2554 /*
2555 * Remember location of down touch.
2556 * ACTION_DOWN always refers to pointer index 0.
2557 */
2558 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002559 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01002560 mActivePointerId = ev.getPointerId(0);
2561
2562 initOrResetVelocityTracker();
2563 mVelocityTracker.addMovement(ev);
2564 /*
2565 * If being flinged and user touches the screen, initiate drag;
2566 * otherwise don't. mScroller.isFinished should be false when
2567 * being flinged.
2568 */
2569 boolean isBeingDragged = !mScroller.isFinished();
2570 setIsBeingDragged(isBeingDragged);
2571 break;
2572 }
2573
2574 case MotionEvent.ACTION_CANCEL:
2575 case MotionEvent.ACTION_UP:
2576 /* Release the drag */
2577 setIsBeingDragged(false);
2578 mActivePointerId = INVALID_POINTER;
2579 recycleVelocityTracker();
2580 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2581 postInvalidateOnAnimation();
2582 }
2583 break;
2584 case MotionEvent.ACTION_POINTER_UP:
2585 onSecondaryPointerUp(ev);
2586 break;
2587 }
2588
2589 /*
2590 * The only time we want to intercept motion events is if we are in the
2591 * drag mode.
2592 */
2593 return mIsBeingDragged;
2594 }
2595
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002596 /**
2597 * @return Whether the specified motion event is actually happening over the content.
2598 */
2599 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01002600 return isInContentBounds(event.getY());
2601 }
2602
2603 /**
2604 * @return Whether a y coordinate is inside the content.
2605 */
2606 public boolean isInContentBounds(float y) {
2607 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002608 }
2609
Selim Cinek67b22602014-03-10 15:40:16 +01002610 private void setIsBeingDragged(boolean isDragged) {
2611 mIsBeingDragged = isDragged;
2612 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002613 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02002614 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002615 }
2616 }
2617
2618 @Override
2619 public void onWindowFocusChanged(boolean hasWindowFocus) {
2620 super.onWindowFocusChanged(hasWindowFocus);
2621 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002622 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002623 }
2624 }
Selim Cinekfab078b2014-03-27 22:45:58 +01002625
Selim Cinek1408eb52014-06-02 14:45:38 +02002626 public void removeLongPressCallback() {
2627 mSwipeHelper.removeLongPressCallback();
2628 }
2629
Selim Cinekfab078b2014-03-27 22:45:58 +01002630 @Override
2631 public boolean isScrolledToTop() {
2632 return mOwnScrollY == 0;
2633 }
2634
2635 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002636 public boolean isScrolledToBottom() {
2637 return mOwnScrollY >= getScrollRange();
2638 }
2639
2640 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01002641 public View getHostView() {
2642 return this;
2643 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002644
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002645 public int getEmptyBottomMargin() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002646 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002647 if (needsHeightAdaption()) {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002648 emptyMargin -= mBottomStackSlowDownHeight;
Jorim Jaggi1d480692014-05-20 19:41:58 +02002649 } else {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002650 emptyMargin -= mCollapseSecondCardPadding;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002651 }
2652 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002653 }
2654
Selim Cinek5f71bee2015-11-18 10:25:23 -08002655 public float getKeyguardBottomStackSize() {
2656 return mBottomStackPeekSize + getResources().getDimensionPixelSize(
2657 R.dimen.bottom_stack_slow_down_length);
2658 }
2659
Selim Cinek1685e632014-04-08 02:27:49 +02002660 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002661 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02002662 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
2663 }
2664
2665 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002666 mIsExpansionChanging = false;
Selim Cinek1685e632014-04-08 02:27:49 +02002667 mStackScrollAlgorithm.onExpansionStopped();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002668 if (!mIsExpanded) {
2669 mOwnScrollY = 0;
Selim Cinekf336f4c2014-11-12 16:58:16 +01002670
2671 // lets make sure nothing is in the overlay anymore
2672 getOverlay().clear();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002673 }
Selim Cinek1685e632014-04-08 02:27:49 +02002674 }
2675
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002676 public void onPanelTrackingStarted() {
2677 mPanelTracking = true;
2678 }
2679 public void onPanelTrackingStopped() {
2680 mPanelTracking = false;
2681 }
2682
Selim Cinekb24e0a92015-06-09 20:17:30 -07002683 public void resetScrollPosition() {
2684 mScroller.abortAnimation();
2685 mOwnScrollY = 0;
2686 }
2687
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02002688 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02002689 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02002690 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02002691 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002692 if (changed) {
Selim Cinek9184f9c2016-02-02 17:36:53 -08002693 if (!mIsExpanded) {
2694 mGroupManager.collapseAllGroups();
2695 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002696 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02002697 updateChronometers();
2698 }
2699 }
2700
2701 private void updateChronometers() {
2702 int childCount = getChildCount();
2703 for (int i = 0; i < childCount; i++) {
2704 updateChronometerForChild(getChildAt(i));
2705 }
2706 }
2707
2708 private void updateChronometerForChild(View child) {
2709 if (child instanceof ExpandableNotificationRow) {
2710 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2711 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002712 }
Selim Cinek1685e632014-04-08 02:27:49 +02002713 }
2714
Jorim Jaggibe565df2014-04-28 17:51:23 +02002715 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01002716 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002717 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002718 updateScrollPositionOnExpandInBottom(view);
2719 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02002720 notifyHeightChangeListener(view);
Selim Cinekb5605e52015-02-20 18:21:41 +01002721 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08002722 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
2723 ? (ExpandableNotificationRow) view
2724 : null;
2725 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01002726 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002727 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02002728 }
2729
Selim Cineka5e211b2014-08-11 17:35:48 +02002730 @Override
2731 public void onReset(ExpandableView view) {
Selim Cinek0e41dea2014-08-25 13:55:06 +02002732 if (mIsExpanded && mAnimationsEnabled) {
2733 mRequestViewResizeAnimationOnLayout = true;
2734 }
Selim Cinek31094df2014-08-14 19:28:15 +02002735 mStackScrollAlgorithm.onReset(view);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002736 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02002737 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02002738 }
2739
Selim Cinekf7a14c02014-07-07 14:01:46 +02002740 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2741 if (view instanceof ExpandableNotificationRow) {
2742 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002743 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinekf7a14c02014-07-07 14:01:46 +02002744 // We are actually expanding this view
Selim Cinek388df6d2015-10-22 13:25:11 -07002745 float endPosition;
2746 if (row.isChildInGroup()) {
2747 ExpandableNotificationRow parent = row.getNotificationParent();
2748 endPosition = parent.getTranslationY() + parent.getActualHeight();
2749 } else {
2750 endPosition = row.getTranslationY() + row.getActualHeight();
2751 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02002752 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
Selim Cinekcb9400a2015-06-03 16:56:13 +02002753 mBottomStackSlowDownHeight + (int) mStackTranslation;
Selim Cinekf7a14c02014-07-07 14:01:46 +02002754 if (endPosition > stackEnd) {
2755 mOwnScrollY += endPosition - stackEnd;
2756 mDisallowScrollingInThisMotion = true;
2757 }
2758 }
2759 }
2760 }
2761
Jorim Jaggibe565df2014-04-28 17:51:23 +02002762 public void setOnHeightChangedListener(
2763 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2764 this.mOnHeightChangedListener = mOnHeightChangedListener;
2765 }
2766
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002767 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2768 mOnEmptySpaceClickListener = listener;
2769 }
2770
Selim Cinek572bbd42014-04-25 16:43:27 +02002771 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08002772 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02002773 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07002774 runAnimationFinishedRunnables();
Selim Cinek0fccc722015-07-29 17:04:36 -07002775 clearViewOverlays();
2776 }
2777
2778 private void clearViewOverlays() {
2779 for (View view : mClearOverlayViewsWhenFinished) {
2780 getOverlay().remove(view);
2781 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002782 }
2783
2784 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002785 for (Runnable runnable : mAnimationFinishedRunnables) {
2786 runnable.run();
2787 }
2788 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002789 }
2790
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002791 /**
2792 * See {@link AmbientState#setDimmed}.
2793 */
2794 public void setDimmed(boolean dimmed, boolean animate) {
2795 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002796 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002797 mDimmedNeedsAnimation = true;
2798 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08002799 animateDimmed(dimmed);
2800 } else {
2801 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002802 }
2803 requestChildrenUpdate();
2804 }
2805
Selim Cinekd35c2792016-01-21 13:20:57 -08002806 private void setDimAmount(float dimAmount) {
2807 mDimAmount = dimAmount;
2808 updateBackgroundDimming();
2809 }
2810
2811 private void animateDimmed(boolean dimmed) {
2812 if (mDimAnimator != null) {
2813 mDimAnimator.cancel();
2814 }
2815 float target = dimmed ? 1.0f : 0.0f;
2816 if (target == mDimAmount) {
2817 return;
2818 }
2819 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
2820 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
2821 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
2822 mDimAnimator.addListener(mDimEndListener);
2823 mDimAnimator.addUpdateListener(mDimUpdateListener);
2824 mDimAnimator.start();
2825 }
2826
Jorim Jaggiae441282014-08-01 02:45:18 +02002827 public void setHideSensitive(boolean hideSensitive, boolean animate) {
2828 if (hideSensitive != mAmbientState.isHideSensitive()) {
2829 int childCount = getChildCount();
2830 for (int i = 0; i < childCount; i++) {
2831 ExpandableView v = (ExpandableView) getChildAt(i);
2832 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2833 }
2834 mAmbientState.setHideSensitive(hideSensitive);
2835 if (animate && mAnimationsEnabled) {
2836 mHideSensitiveNeedsAnimation = true;
2837 mNeedsAnimation = true;
2838 }
2839 requestChildrenUpdate();
2840 }
2841 }
2842
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002843 /**
2844 * See {@link AmbientState#setActivatedChild}.
2845 */
Selim Cineka32ab602014-06-11 15:06:01 +02002846 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002847 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002848 if (mAnimationsEnabled) {
2849 mActivateNeedsAnimation = true;
2850 mNeedsAnimation = true;
2851 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002852 requestChildrenUpdate();
2853 }
2854
Selim Cineka32ab602014-06-11 15:06:01 +02002855 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002856 return mAmbientState.getActivatedChild();
2857 }
2858
Selim Cinek572bbd42014-04-25 16:43:27 +02002859 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02002860 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02002861 if (mListener != null) {
2862 mListener.onChildLocationsChanged(this);
2863 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002864 runAnimationFinishedRunnables();
Selim Cinek6811d722016-01-19 17:53:12 -08002865 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08002866 updateViewShadows();
2867 }
2868
2869 private void updateViewShadows() {
2870 // we need to work around an issue where the shadow would not cast between siblings when
2871 // their z difference is between 0 and 0.1
2872
2873 // Lefts first sort by Z difference
2874 for (int i = 0; i < getChildCount(); i++) {
2875 ExpandableView child = (ExpandableView) getChildAt(i);
2876 if (child.getVisibility() != GONE) {
2877 mTmpSortedChildren.add(child);
2878 }
2879 }
2880 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
2881
2882 // Now lets update the shadow for the views
2883 ExpandableView previous = null;
2884 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
2885 ExpandableView expandableView = mTmpSortedChildren.get(i);
2886 float translationZ = expandableView.getTranslationZ();
2887 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
2888 float diff = otherZ - translationZ;
2889 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
2890 // There is no fake shadow to be drawn
2891 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
2892 } else {
2893 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
2894 expandableView.getTranslationY();
2895 expandableView.setFakeShadowIntensity(diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
2896 previous.getOutlineAlpha(), (int) yLocation,
2897 previous.getOutlineTranslation());
2898 }
2899 previous = expandableView;
2900 }
2901
2902 mTmpSortedChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002903 }
2904
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002905 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002906 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002907 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002908 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002909 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002910 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002911 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02002912 }
2913
Selim Cinek1408eb52014-06-02 14:45:38 +02002914 public void cancelExpandHelper() {
2915 mExpandHelper.cancel();
2916 }
2917
2918 public void setIntrinsicPadding(int intrinsicPadding) {
2919 mIntrinsicPadding = intrinsicPadding;
2920 }
2921
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002922 public int getIntrinsicPadding() {
2923 return mIntrinsicPadding;
2924 }
2925
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002926 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02002927 * @return the y position of the first notification
2928 */
2929 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07002930 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02002931 }
2932
Selim Cinekc0ce82d2014-06-10 13:21:15 +02002933 @Override
2934 public boolean shouldDelayChildPressedState() {
2935 return true;
2936 }
2937
Jorim Jaggi457cc352014-06-02 22:47:42 +02002938 /**
John Spurlockbf370992014-06-17 13:58:31 -04002939 * See {@link AmbientState#setDark}.
2940 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002941 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04002942 mAmbientState.setDark(dark);
2943 if (animate && mAnimationsEnabled) {
2944 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002945 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04002946 mNeedsAnimation = true;
2947 }
2948 requestChildrenUpdate();
Selim Cinek6811d722016-01-19 17:53:12 -08002949 if (dark) {
2950 setWillNotDraw(!DEBUG);
2951 mScrimController.setExcludedBackgroundArea(null);
2952 } else {
2953 updateBackground();
2954 setWillNotDraw(false);
2955 // TODO: fade in background
2956 }
John Spurlockbf370992014-06-17 13:58:31 -04002957 }
2958
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002959 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
2960 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
2961 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2962 }
2963 if (screenLocation.y > getBottomMostNotificationBottom()) {
2964 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
2965 }
2966 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
2967 if (child != null) {
2968 return getNotGoneIndex(child);
2969 } else {
2970 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2971 }
2972 }
2973
2974 private int getNotGoneIndex(View child) {
2975 int count = getChildCount();
2976 int notGoneIndex = 0;
2977 for (int i = 0; i < count; i++) {
2978 View v = getChildAt(i);
2979 if (child == v) {
2980 return notGoneIndex;
2981 }
2982 if (v.getVisibility() != View.GONE) {
2983 notGoneIndex++;
2984 }
2985 }
2986 return -1;
2987 }
2988
Dan Sandlereceda3d2014-07-21 15:35:01 -04002989 public void setDismissView(DismissView dismissView) {
Selim Cinek01af3342016-02-09 19:25:31 -08002990 int index = -1;
2991 if (mDismissView != null) {
2992 index = indexOfChild(mDismissView);
2993 removeView(mDismissView);
2994 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002995 mDismissView = dismissView;
Selim Cinek01af3342016-02-09 19:25:31 -08002996 addView(mDismissView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002997 }
2998
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002999 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08003000 int index = -1;
3001 if (mEmptyShadeView != null) {
3002 index = indexOfChild(mEmptyShadeView);
3003 removeView(mEmptyShadeView);
3004 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003005 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08003006 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003007 }
3008
3009 public void updateEmptyShadeView(boolean visible) {
3010 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
3011 int newVisibility = visible ? VISIBLE : GONE;
3012 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003013 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003014 if (mEmptyShadeView.willBeGone()) {
3015 mEmptyShadeView.cancelAnimation();
3016 } else {
3017 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003018 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003019 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003020 mEmptyShadeView.setWillBeGone(false);
3021 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003022 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003023 } else {
Selim Cinek20867102014-12-10 17:09:17 +01003024 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003025 @Override
3026 public void run() {
3027 mEmptyShadeView.setVisibility(GONE);
3028 mEmptyShadeView.setWillBeGone(false);
3029 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003030 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003031 }
Selim Cinek20867102014-12-10 17:09:17 +01003032 };
3033 if (mAnimationsEnabled) {
3034 mEmptyShadeView.setWillBeGone(true);
3035 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
3036 } else {
3037 mEmptyShadeView.setInvisible();
3038 onFinishedRunnable.run();
3039 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003040 }
3041 }
3042 }
3043
Selim Cinek2cd45df2015-06-09 18:00:07 -07003044 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
3045 mOverflowContainer = overFlowContainer;
3046 addView(mOverflowContainer);
3047 }
3048
3049 public void updateOverflowContainerVisibility(boolean visible) {
3050 int oldVisibility = mOverflowContainer.willBeGone() ? GONE
3051 : mOverflowContainer.getVisibility();
3052 final int newVisibility = visible ? VISIBLE : GONE;
3053 if (oldVisibility != newVisibility) {
3054 Runnable onFinishedRunnable = new Runnable() {
3055 @Override
3056 public void run() {
3057 mOverflowContainer.setVisibility(newVisibility);
3058 mOverflowContainer.setWillBeGone(false);
3059 updateContentHeight();
3060 notifyHeightChangeListener(mOverflowContainer);
3061 }
3062 };
3063 if (!mAnimationsEnabled || !mIsExpanded) {
3064 mOverflowContainer.cancelAppearDrawing();
3065 onFinishedRunnable.run();
3066 } else if (newVisibility != GONE) {
3067 mOverflowContainer.performAddAnimation(0,
3068 StackStateAnimator.ANIMATION_DURATION_STANDARD);
3069 mOverflowContainer.setVisibility(newVisibility);
3070 mOverflowContainer.setWillBeGone(false);
3071 updateContentHeight();
3072 notifyHeightChangeListener(mOverflowContainer);
3073 } else {
3074 mOverflowContainer.performRemoveAnimation(
3075 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3076 0.0f,
3077 onFinishedRunnable);
3078 mOverflowContainer.setWillBeGone(true);
3079 }
3080 }
3081 }
3082
Dan Sandlereceda3d2014-07-21 15:35:01 -04003083 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003084 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003085 int newVisibility = visible ? VISIBLE : GONE;
3086 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003087 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003088 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003089 mDismissView.cancelAnimation();
3090 } else {
3091 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003092 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003093 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003094 mDismissView.setWillBeGone(false);
3095 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003096 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003097 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003098 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003099 @Override
3100 public void run() {
3101 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003102 mDismissView.setWillBeGone(false);
3103 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003104 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003105 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01003106 };
Selim Cinek20867102014-12-10 17:09:17 +01003107 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003108 mDismissView.setWillBeGone(true);
3109 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
3110 } else {
3111 dimissHideFinishRunnable.run();
Selim Cinek7d5f3742014-11-07 18:07:49 +01003112 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003113 }
3114 }
3115 }
3116
3117 public void setDismissAllInProgress(boolean dismissAllInProgress) {
3118 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07003119 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cineka272dfe2015-02-20 18:12:28 +01003120 if (dismissAllInProgress) {
3121 disableClipOptimization();
3122 }
Selim Cinek9c17b772015-07-07 20:37:09 -07003123 handleDismissAllClipping();
Mady Mellor3cb40242016-02-22 10:43:35 -08003124 if (mCurrIconRow != null && mCurrIconRow.isVisible()) {
Mady Mellor4b80b102016-01-22 08:03:58 -08003125 mCurrIconRow.getNotificationParent().animateTranslateNotification(0 /* left target */);
3126 }
Selim Cinek9c17b772015-07-07 20:37:09 -07003127 }
3128
3129 private void handleDismissAllClipping() {
3130 final int count = getChildCount();
3131 boolean previousChildWillBeDismissed = false;
3132 for (int i = 0; i < count; i++) {
3133 ExpandableView child = (ExpandableView) getChildAt(i);
3134 if (child.getVisibility() == GONE) {
3135 continue;
3136 }
3137 if (mDismissAllInProgress && previousChildWillBeDismissed) {
3138 child.setMinClipTopAmount(child.getClipTopAmount());
3139 } else {
3140 child.setMinClipTopAmount(0);
3141 }
3142 previousChildWillBeDismissed = canChildBeDismissed(child);
3143 }
Selim Cineka272dfe2015-02-20 18:12:28 +01003144 }
3145
3146 private void disableClipOptimization() {
3147 final int count = getChildCount();
3148 for (int i = 0; i < count; i++) {
3149 ExpandableView child = (ExpandableView) getChildAt(i);
3150 if (child.getVisibility() == GONE) {
3151 continue;
3152 }
3153 child.setClipTopOptimization(0);
3154 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003155 }
3156
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003157 public boolean isDismissViewNotGone() {
3158 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
3159 }
3160
3161 public boolean isDismissViewVisible() {
3162 return mDismissView.isVisible();
3163 }
3164
3165 public int getDismissViewHeight() {
Selim Cinek61633a82016-01-25 15:54:10 -08003166 int height = mDismissView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi1d49ec92014-08-25 18:44:01 +02003167
3168 // Hack: Accommodate for additional distance when we only have one notification and the
3169 // dismiss all button.
3170 if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView
3171 && getFirstChildNotGone() instanceof ActivatableNotificationView) {
3172 height += mCollapseSecondCardPadding;
3173 }
3174 return height;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003175 }
3176
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01003177 public int getEmptyShadeViewHeight() {
3178 return mEmptyShadeView.getHeight();
3179 }
3180
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003181 public float getBottomMostNotificationBottom() {
3182 final int count = getChildCount();
3183 float max = 0;
3184 for (int childIdx = 0; childIdx < count; childIdx++) {
3185 ExpandableView child = (ExpandableView) getChildAt(childIdx);
3186 if (child.getVisibility() == GONE) {
3187 continue;
3188 }
3189 float bottom = child.getTranslationY() + child.getActualHeight();
3190 if (bottom > max) {
3191 max = bottom;
3192 }
3193 }
Selim Cinekd2281152015-04-10 14:37:46 -07003194 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003195 }
3196
Selim Cinek19c8c702014-08-25 22:09:19 +02003197 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
3198 this.mPhoneStatusBar = phoneStatusBar;
3199 }
3200
Selim Cinekb5605e52015-02-20 18:21:41 +01003201 public void setGroupManager(NotificationGroupManager groupManager) {
3202 this.mGroupManager = groupManager;
3203 }
3204
Selim Cinekd9acca52014-09-01 22:33:25 +02003205 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01003206 requestAnimateEverything();
3207 }
3208
3209 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02003210 if (mIsExpanded && mAnimationsEnabled) {
3211 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01003212 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02003213 requestChildrenUpdate();
3214 }
3215 }
3216
Selim Cinek04fb2582015-06-02 19:58:09 +02003217 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01003218 int childCount = getChildCount();
3219 for (int i = childCount - 1; i >= 0; i--) {
3220 ExpandableView child = (ExpandableView) getChildAt(i);
3221 if (child.getVisibility() != View.GONE) {
3222 float childTop = child.getY();
3223 if (childTop > touchY) {
3224 // we are above a notification entirely let's abort
3225 return false;
3226 }
3227 boolean belowChild = touchY > childTop + child.getActualHeight();
3228 if (child == mDismissView) {
3229 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
3230 touchY - childTop)) {
3231 // We clicked on the dismiss button
3232 return false;
3233 }
3234 } else if (child == mEmptyShadeView) {
3235 // We arrived at the empty shade view, for which we accept all clicks
3236 return true;
3237 } else if (!belowChild){
3238 // We are on a child
3239 return false;
3240 }
3241 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003242 }
Selim Cinek04fb2582015-06-02 19:58:09 +02003243 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003244 }
3245
Selim Cinekb5605e52015-02-20 18:21:41 +01003246 @Override
3247 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08003248 boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
Selim Cinekb5605e52015-02-20 18:21:41 +01003249 if (animated) {
3250 mExpandedGroupView = changedRow;
3251 mNeedsAnimation = true;
3252 }
3253 changedRow.setChildrenExpanded(expanded, animated);
3254 onHeightChanged(changedRow, false /* needsAnimation */);
3255 }
3256
3257 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01003258 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
Selim Cinekef5127e2015-12-21 16:55:58 -08003259 mPhoneStatusBar.requestNotificationUpdate();
3260 }
3261
3262 @Override
3263 public void onChildIsolationChanged() {
3264 mPhoneStatusBar.requestNotificationUpdate();
Selim Cinekb5605e52015-02-20 18:21:41 +01003265 }
3266
3267 public void generateChildOrderChangedEvent() {
3268 if (mIsExpanded && mAnimationsEnabled) {
3269 mGenerateChildOrderChangedEvent = true;
3270 mNeedsAnimation = true;
3271 requestChildrenUpdate();
3272 }
3273 }
3274
Selim Cinek684a4422015-04-15 16:18:39 -07003275 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003276 mAnimationFinishedRunnables.add(runnable);
3277 }
3278
3279 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
3280 mHeadsUpManager = headsUpManager;
3281 mAmbientState.setHeadsUpManager(headsUpManager);
3282 }
3283
3284 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
3285 if (mAnimationsEnabled) {
3286 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
3287 mNeedsAnimation = true;
3288 requestChildrenUpdate();
3289 }
3290 }
3291
3292 public void setShadeExpanded(boolean shadeExpanded) {
3293 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07003294 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003295 }
3296
Selim Cineka59ecc32015-04-07 10:51:49 -07003297 /**
3298 * Set the boundary for the bottom heads up position. The heads up will always be above this
3299 * position.
3300 *
3301 * @param height the height of the screen
3302 * @param bottomBarHeight the height of the bar on the bottom
3303 */
3304 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
3305 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
3306 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003307 requestChildrenUpdate();
3308 }
3309
3310 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
3311 mTrackingHeadsUp = trackingHeadsUp;
3312 }
3313
Selim Cinekaac93252015-04-14 20:04:12 -07003314 public void setScrimController(ScrimController scrimController) {
3315 mScrimController = scrimController;
Selim Cinekd35c2792016-01-21 13:20:57 -08003316 mScrimController.setScrimBehindChangeRunnable(new Runnable() {
3317 @Override
3318 public void run() {
3319 updateBackgroundDimming();
3320 }
3321 });
Selim Cinekaac93252015-04-14 20:04:12 -07003322 }
3323
Selim Cinekbbc580b2015-06-03 14:11:03 +02003324 public void forceNoOverlappingRendering(boolean force) {
3325 mForceNoOverlappingRendering = force;
3326 }
3327
3328 @Override
3329 public boolean hasOverlappingRendering() {
3330 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
3331 }
3332
Selim Cinek6811d722016-01-19 17:53:12 -08003333 public void setAnimationRunning(boolean animationRunning) {
3334 if (animationRunning != mAnimationRunning) {
3335 if (animationRunning) {
3336 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
3337 } else {
3338 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
3339 }
3340 mAnimationRunning = animationRunning;
Selim Cinek33223572016-02-19 19:32:22 -08003341 updateContinuousShadowDrawing();
Selim Cinek6811d722016-01-19 17:53:12 -08003342 }
3343 }
3344
Selim Cinek3776fe02016-02-04 13:32:43 -08003345 public boolean isExpanded() {
3346 return mIsExpanded;
3347 }
3348
Selim Cinek3afd00e2014-08-11 22:32:57 +02003349 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003350 * A listener that is notified when some child locations might have changed.
3351 */
3352 public interface OnChildLocationsChangedListener {
3353 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
3354 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003355
Jorim Jaggi290600a2014-05-30 17:02:20 +02003356 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003357 * A listener that is notified when the empty space below the notifications is clicked on
3358 */
3359 public interface OnEmptySpaceClickListener {
3360 public void onEmptySpaceClicked(float x, float y);
3361 }
3362
3363 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02003364 * A listener that gets notified when the overscroll at the top has changed.
3365 */
3366 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02003367
3368 /**
3369 * Notifies a listener that the overscroll has changed.
3370 *
3371 * @param amount the amount of overscroll, in pixels
3372 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
3373 * unrubberbanded motion to directly expand overscroll view (e.g expand
3374 * QS)
3375 */
3376 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02003377
3378 /**
3379 * Notify a listener that the scroller wants to escape from the scrolling motion and
3380 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
3381 *
3382 * @param velocity The velocity that the Scroller had when over flinging
3383 * @param open Should the fling open or close the overscroll view.
3384 */
3385 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02003386 }
3387
Mady Mellor4b80b102016-01-22 08:03:58 -08003388 /**
3389 * A listener that is notified when the gear is shown behind a notification.
3390 */
3391 public interface GearDisplayedListener {
3392 void onGearDisplayed(ExpandableNotificationRow row);
3393 }
3394
3395 private class NotificationSwipeHelper extends SwipeHelper {
3396 private static final int MOVE_STATE_LEFT = -1;
3397 private static final int MOVE_STATE_UNDEFINED = 0;
3398 private static final int MOVE_STATE_RIGHT = 1;
3399
3400 private static final long GEAR_SHOW_DELAY = 60;
3401
3402 private ArrayList<View> mTranslatingViews = new ArrayList<>();
3403 private CheckForDrag mCheckForDrag;
3404 private Handler mHandler;
3405 private int mMoveState = MOVE_STATE_UNDEFINED;
3406
3407 public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
3408 super(swipeDirection, callback, context);
3409 mHandler = new Handler();
3410 }
3411
3412 @Override
3413 public void onDownUpdate(View currView) {
3414 // Set the active view
3415 mTranslatingParentView = currView;
3416
3417 // Reset check for drag gesture
3418 mCheckForDrag = null;
3419
3420 // Slide back any notifications that might be showing a gear
3421 resetExposedGearView();
3422
3423 if (currView instanceof ExpandableNotificationRow) {
3424 // Set the listener for the current row's gear
3425 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
3426 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
3427
3428 // And the translating children
3429 mTranslatingViews = ((ExpandableNotificationRow) currView).getContentViews();
3430 }
3431 mMoveState = MOVE_STATE_UNDEFINED;
3432 }
3433
3434 @Override
3435 public void onMoveUpdate(View view, float translation, float delta) {
3436 final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT;
3437 if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) {
3438 // Changed directions, make sure we check for drag again.
3439 mCheckForDrag = null;
3440 }
3441 mMoveState = newMoveState;
3442
3443 if (view instanceof ExpandableNotificationRow) {
3444 ((ExpandableNotificationRow) view).setTranslationForOutline(translation);
3445 if (!isPinnedHeadsUp(view)) {
3446 // Only show the gear if we're not a heads up view.
3447 checkForDrag();
3448 if (mCurrIconRow != null) {
3449 mCurrIconRow.updateSettingsIcons(translation, getSize(view));
3450 }
3451 }
3452 }
3453 }
3454
3455 @Override
3456 public void dismissChild(final View view, float velocity) {
3457 cancelCheckForDrag();
3458 super.dismissChild(view, velocity);
3459 }
3460
3461 @Override
3462 public void snapChild(final View animView, final float targetLeft, float velocity) {
3463 final float snapBackThreshold = getSpaceForGear(animView);
3464 final float translation = getTranslation(animView);
3465 final boolean fromLeft = translation > 0;
3466 final float absTrans = Math.abs(translation);
3467 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
3468
3469 boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f
3470 && translation <= notiThreshold) ||
3471 (!fromLeft && absTrans >= snapBackThreshold * 0.4f
3472 && absTrans <= notiThreshold);
3473
3474 if (pastGear && !isPinnedHeadsUp(animView)) {
3475 // bouncity
3476 final float target = fromLeft ? snapBackThreshold : -snapBackThreshold;
3477 mGearExposedView = mTranslatingParentView;
3478 if (mGearDisplayedListener != null
3479 && (animView instanceof ExpandableNotificationRow)) {
3480 mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView);
3481 }
3482 super.snapChild(animView, target, velocity);
3483 } else {
3484 super.snapChild(animView, 0, velocity);
3485 }
3486 }
3487
3488 @Override
3489 public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
3490 if (mDismissAllInProgress) {
3491 // When dismissing all, we translate the entire view instead.
3492 super.onTranslationUpdate(animView, value, canBeDismissed);
3493 return;
3494 }
3495 if (animView instanceof ExpandableNotificationRow) {
3496 ((ExpandableNotificationRow) animView).setTranslationForOutline(value);
3497 }
3498 if (mCurrIconRow != null) {
3499 mCurrIconRow.updateSettingsIcons(value, getSize(animView));
3500 }
3501 }
3502
3503 @Override
3504 public Animator getViewTranslationAnimator(View v, float target,
3505 AnimatorUpdateListener listener) {
3506 if (mDismissAllInProgress) {
3507 // When dismissing all, we translate the entire view instead.
3508 return super.getViewTranslationAnimator(v, target, listener);
3509 }
3510 ArrayList<Animator> animators = new ArrayList<Animator>();
3511 for (int i = 0; i < mTranslatingViews.size(); i++) {
3512 ObjectAnimator anim = createTranslationAnimation(mTranslatingViews.get(i), target);
3513 animators.add(anim);
3514 if (i == 0 && listener != null) {
3515 anim.addUpdateListener(listener);
3516 }
3517 }
3518 AnimatorSet set = new AnimatorSet();
3519 set.playTogether(animators);
3520 return set;
3521 }
3522
3523 @Override
3524 public void setTranslation(View v, float translate) {
3525 if (mDismissAllInProgress) {
3526 // When dismissing all, we translate the entire view instead.
3527 super.setTranslation(v, translate);
3528 return;
3529 }
3530 // Translate the group of views
3531 for (int i = 0; i < mTranslatingViews.size(); i++) {
3532 if (mTranslatingViews.get(i) != null) {
3533 super.setTranslation(mTranslatingViews.get(i), translate);
3534 }
3535 }
3536 }
3537
3538 @Override
3539 public float getTranslation(View v) {
3540 if (mDismissAllInProgress) {
3541 // When dismissing all, we translate the entire view instead.
3542 return super.getTranslation(v);
3543 }
3544 // All of the views in the list should have same translation, just use first one.
3545 if (mTranslatingViews.size() > 0) {
3546 return super.getTranslation(mTranslatingViews.get(0));
3547 }
3548 return 0;
3549 }
3550
3551
3552 /**
3553 * Returns the horizontal space in pixels required to display the gear behind a
3554 * notification.
3555 */
3556 private float getSpaceForGear(View view) {
3557 if (view instanceof ExpandableNotificationRow) {
3558 return ((ExpandableNotificationRow) view).getSpaceForGear();
3559 }
3560 return 0;
3561 }
3562
3563 private void checkForDrag() {
3564 if (mCheckForDrag == null) {
3565 mCheckForDrag = new CheckForDrag();
3566 mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY);
3567 }
3568 }
3569
3570 private void cancelCheckForDrag() {
3571 if (mCurrIconRow != null) {
3572 mCurrIconRow.cancelFadeAnimator();
3573 }
3574 mHandler.removeCallbacks(mCheckForDrag);
3575 mCheckForDrag = null;
3576 }
3577
3578 private final class CheckForDrag implements Runnable {
3579 @Override
3580 public void run() {
3581 final float translation = getTranslation(mTranslatingParentView);
3582 final float absTransX = Math.abs(translation);
3583 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
3584 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
3585 if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4
3586 && absTransX < notiThreshold) {
3587 // Show icon
3588 mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
3589 notiThreshold);
3590 } else {
3591 // Allow more to be posted if this wasn't a drag.
3592 mCheckForDrag = null;
3593 }
3594 }
3595 }
3596
3597 private void resetExposedGearView() {
3598 if (mGearExposedView == null || mGearExposedView == mTranslatingParentView) {
3599 // If no gear is showing or it's showing for this view we do nothing.
3600 return;
3601 }
3602
3603 final View prevGearExposedView = mGearExposedView;
3604 mGearExposedView = null;
3605
3606 AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
3607 public void onAnimationEnd(Animator animator) {
3608 if (prevGearExposedView instanceof ExpandableNotificationRow) {
3609 ((ExpandableNotificationRow) prevGearExposedView).getSettingsRow()
3610 .resetState();
3611 }
3612 }
3613 };
3614 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
3615 @Override
3616 public void onAnimationUpdate(ValueAnimator animation) {
3617 if (prevGearExposedView instanceof ExpandableNotificationRow) {
3618 ((ExpandableNotificationRow) prevGearExposedView)
3619 .setTranslationForOutline((float) animation.getAnimatedValue());
3620 }
3621 }
3622 };
3623 Animator set = getViewTranslationAnimator(prevGearExposedView, 0, updateListener);
3624 set.addListener(listener);
3625 set.start();
3626 }
3627 }
3628
Selim Cinek33223572016-02-19 19:32:22 -08003629 private void updateContinuousShadowDrawing() {
3630 boolean continuousShadowUpdate = mAnimationRunning
3631 || !mAmbientState.getDraggedViews().isEmpty();
3632 if (continuousShadowUpdate != mContinuousShadowUpdate) {
3633 if (continuousShadowUpdate) {
3634 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
3635 } else {
3636 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
3637 }
3638 }
3639 }
3640
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003641 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02003642
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003643 static AnimationFilter[] FILTERS = new AnimationFilter[] {
3644
3645 // ANIMATION_TYPE_ADD
3646 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003647 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003648 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003649 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003650 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003651 .animateZ()
3652 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003653
3654 // ANIMATION_TYPE_REMOVE
3655 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003656 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003657 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003658 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003659 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003660 .animateZ()
3661 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003662
3663 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3664 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003665 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003666 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003667 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003668 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003669 .animateZ()
3670 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003671
3672 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3673 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003674 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003675 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003676 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003677 .animateY()
3678 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003679 .animateZ(),
3680
3681 // ANIMATION_TYPE_START_DRAG
3682 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003683 .animateShadowAlpha(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003684
3685 // ANIMATION_TYPE_SNAP_BACK
3686 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003687 .animateShadowAlpha()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003688 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003689
3690 // ANIMATION_TYPE_ACTIVATED_CHILD
3691 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003692 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003693
3694 // ANIMATION_TYPE_DIMMED
3695 new AnimationFilter()
Selim Cinek34c0a8d2014-05-12 00:01:43 +02003696 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003697 .animateDimmed(),
3698
3699 // ANIMATION_TYPE_CHANGE_POSITION
3700 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003701 .animateAlpha() // maybe the children change positions
3702 .animateShadowAlpha()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003703 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003704 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003705 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04003706 .animateZ(),
3707
3708 // ANIMATION_TYPE_DARK
3709 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01003710 .animateDark()
3711 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003712
3713 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3714 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003715 .animateShadowAlpha()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003716 .animateHeight()
3717 .animateTopInset()
3718 .animateY()
3719 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02003720 .animateZ()
3721 .hasDelays(),
3722
3723 // ANIMATION_TYPE_HIDE_SENSITIVE
3724 new AnimationFilter()
3725 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02003726
3727 // ANIMATION_TYPE_VIEW_RESIZE
3728 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003729 .animateShadowAlpha()
Selim Cineka5e211b2014-08-11 17:35:48 +02003730 .animateHeight()
3731 .animateTopInset()
3732 .animateY()
3733 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02003734
Selim Cinekb5605e52015-02-20 18:21:41 +01003735 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
3736 new AnimationFilter()
3737 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003738 .animateShadowAlpha()
Selim Cinekb5605e52015-02-20 18:21:41 +01003739 .animateHeight()
3740 .animateTopInset()
3741 .animateY()
3742 .animateZ(),
3743
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003744 // ANIMATION_TYPE_HEADS_UP_APPEAR
3745 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003746 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003747 .animateHeight()
3748 .animateTopInset()
3749 .animateY()
3750 .animateZ(),
3751
3752 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3753 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003754 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003755 .animateHeight()
3756 .animateTopInset()
3757 .animateY()
3758 .animateZ(),
3759
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003760 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3761 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003762 .animateShadowAlpha()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003763 .animateHeight()
3764 .animateTopInset()
3765 .animateY()
3766 .animateZ()
3767 .hasDelays(),
3768
Selim Cineka59ecc32015-04-07 10:51:49 -07003769 // ANIMATION_TYPE_HEADS_UP_OTHER
3770 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003771 .animateShadowAlpha()
Selim Cineka59ecc32015-04-07 10:51:49 -07003772 .animateHeight()
3773 .animateTopInset()
3774 .animateY()
3775 .animateZ(),
3776
Selim Cinekd9acca52014-09-01 22:33:25 +02003777 // ANIMATION_TYPE_EVERYTHING
3778 new AnimationFilter()
3779 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003780 .animateShadowAlpha()
Selim Cinekd9acca52014-09-01 22:33:25 +02003781 .animateDark()
Selim Cinekd9acca52014-09-01 22:33:25 +02003782 .animateDimmed()
3783 .animateHideSensitive()
3784 .animateHeight()
3785 .animateTopInset()
3786 .animateY()
3787 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003788 };
3789
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003790 static int[] LENGTHS = new int[] {
3791
3792 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003793 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003794
3795 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003796 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003797
3798 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3799 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3800
3801 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3802 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3803
3804 // ANIMATION_TYPE_START_DRAG
3805 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3806
3807 // ANIMATION_TYPE_SNAP_BACK
3808 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3809
3810 // ANIMATION_TYPE_ACTIVATED_CHILD
3811 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
3812
3813 // ANIMATION_TYPE_DIMMED
3814 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003815
3816 // ANIMATION_TYPE_CHANGE_POSITION
3817 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04003818
3819 // ANIMATION_TYPE_DARK
3820 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003821
3822 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3823 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02003824
3825 // ANIMATION_TYPE_HIDE_SENSITIVE
3826 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02003827
3828 // ANIMATION_TYPE_VIEW_RESIZE
3829 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02003830
Selim Cinekb5605e52015-02-20 18:21:41 +01003831 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08003832 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01003833
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003834 // ANIMATION_TYPE_HEADS_UP_APPEAR
3835 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
3836
3837 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3838 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3839
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003840 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3841 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3842
Selim Cineka59ecc32015-04-07 10:51:49 -07003843 // ANIMATION_TYPE_HEADS_UP_OTHER
3844 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3845
Selim Cinekd9acca52014-09-01 22:33:25 +02003846 // ANIMATION_TYPE_EVERYTHING
3847 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003848 };
3849
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003850 static final int ANIMATION_TYPE_ADD = 0;
3851 static final int ANIMATION_TYPE_REMOVE = 1;
3852 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
3853 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
3854 static final int ANIMATION_TYPE_START_DRAG = 4;
3855 static final int ANIMATION_TYPE_SNAP_BACK = 5;
3856 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
3857 static final int ANIMATION_TYPE_DIMMED = 7;
3858 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04003859 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003860 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02003861 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02003862 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01003863 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003864 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
3865 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003866 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
3867 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
3868 static final int ANIMATION_TYPE_EVERYTHING = 18;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003869
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003870 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
3871 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
3872
Selim Cinek572bbd42014-04-25 16:43:27 +02003873 final long eventStartTime;
3874 final View changingView;
3875 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003876 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003877 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003878 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003879 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07003880 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02003881
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003882 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003883 this(view, type, LENGTHS[type]);
3884 }
3885
3886 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003887 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
3888 changingView = view;
3889 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003890 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003891 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003892 }
3893
3894 /**
3895 * Combines the length of several animation events into a single value.
3896 *
3897 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003898 * @return The combined length. Depending on the event types, this might be the maximum of
3899 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003900 */
3901 static long combineLength(ArrayList<AnimationEvent> events) {
3902 long length = 0;
3903 int size = events.size();
3904 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003905 AnimationEvent event = events.get(i);
3906 length = Math.max(length, event.length);
3907 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
3908 return event.length;
3909 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003910 }
3911 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02003912 }
3913 }
3914
Selim Cinek67b22602014-03-10 15:40:16 +01003915}