blob: c9eecc2d3354197f6d9f5dce25376dae6724597e [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
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010019import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010020import android.content.Context;
21import android.content.res.Configuration;
Selim Cinek67b22602014-03-10 15:40:16 +010022import android.graphics.Canvas;
Selim Cinek67b22602014-03-10 15:40:16 +010023import android.graphics.Paint;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010024import android.graphics.PointF;
Selim Cinek67b22602014-03-10 15:40:16 +010025import android.util.AttributeSet;
26import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070027import android.util.Pair;
Selim Cinek67b22602014-03-10 15:40:16 +010028import android.view.MotionEvent;
29import android.view.VelocityTracker;
30import android.view.View;
31import android.view.ViewConfiguration;
32import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020033import android.view.ViewTreeObserver;
Selim Cinek572bbd42014-04-25 16:43:27 +020034import android.view.animation.AnimationUtils;
Selim Cinek67b22602014-03-10 15:40:16 +010035import android.widget.OverScroller;
Jorim Jaggi56306252014-07-03 00:40:09 +020036
Selim Cinek67b22602014-03-10 15:40:16 +010037import com.android.systemui.ExpandHelper;
38import com.android.systemui.R;
39import com.android.systemui.SwipeHelper;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070040import com.android.systemui.classifier.FalsingManager;
Selim Cineka32ab602014-06-11 15:06:01 +020041import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040042import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020043import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010044import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020045import com.android.systemui.statusbar.ExpandableView;
Selim Cinek2cd45df2015-06-09 18:00:07 -070046import com.android.systemui.statusbar.NotificationOverflowContainer;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010047import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020048import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb5605e52015-02-20 18:21:41 +010049import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020050import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070051import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070052import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010053import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010054
Selim Cinek572bbd42014-04-25 16:43:27 +020055import java.util.ArrayList;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020056import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020057
Selim Cinek67b22602014-03-10 15:40:16 +010058/**
59 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
60 */
61public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020062 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Selim Cinekb5605e52015-02-20 18:21:41 +010063 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener {
Selim Cinek67b22602014-03-10 15:40:16 +010064
65 private static final String TAG = "NotificationStackScrollLayout";
66 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020067 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
68 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020069 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010070
71 /**
72 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
73 */
74 private static final int INVALID_POINTER = -1;
75
Selim Cinek1408eb52014-06-02 14:45:38 +020076 private ExpandHelper mExpandHelper;
Selim Cinek67b22602014-03-10 15:40:16 +010077 private SwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +020078 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +010079 private int mCurrentStackHeight = Integer.MAX_VALUE;
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +010080
81 /**
82 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
83 * externally from {@link #setStackHeight}
84 */
85 private float mLastSetStackHeight;
Selim Cinek67b22602014-03-10 15:40:16 +010086 private int mOwnScrollY;
87 private int mMaxLayoutHeight;
88
89 private VelocityTracker mVelocityTracker;
90 private OverScroller mScroller;
91 private int mTouchSlop;
92 private int mMinimumVelocity;
93 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +010094 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +020095 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +010096 private boolean mIsBeingDragged;
97 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +020098 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +010099 private int mActivePointerId;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100100 private boolean mTouchIsClick;
101 private float mInitialTouchX;
102 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100103
Selim Cinek67b22602014-03-10 15:40:16 +0100104 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100105 private int mContentHeight;
106 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200107 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100108 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100109 private int mPaddingBetweenElements;
Selim Cineka5eaa602014-05-12 21:27:47 +0200110 private int mPaddingBetweenElementsNormal;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200111 private int mTopPadding;
Selim Cinekd83771e2014-07-04 16:45:31 +0200112 private int mCollapseSecondCardPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100113
114 /**
115 * The algorithm which calculates the properties for our children
116 */
Selim Cinekaf0dc312015-12-15 17:01:44 -0800117 private final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100118
119 /**
120 * The current State this Layout is in
121 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200122 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200123 private AmbientState mAmbientState = new AmbientState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100124 private NotificationGroupManager mGroupManager;
Selim Cineka59ecc32015-04-07 10:51:49 -0700125 private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
126 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
127 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
128 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
129 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
130 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200131 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700132 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
133 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200134 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200135 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200136 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800137 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200138
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200139 /**
140 * The raw amount of the overScroll on the top, which is not rubber-banded.
141 */
142 private float mOverScrolledTopPixels;
143
144 /**
145 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
146 */
147 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200148 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200149 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200150 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100151 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200152 private boolean mNeedsAnimation;
153 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200154 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200155 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400156 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100157 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200158 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200159 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200160 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200161 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200162 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700163 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200164 private boolean mExpandingNotification;
165 private boolean mExpandedInThisMotion;
166 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400167 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200168 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400169 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200170
171 /**
172 * Was the scroller scrolled to the top when the down motion was observed?
173 */
174 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200175 /**
176 * The minimal amount of over scroll which is needed in order to switch to the quick settings
177 * when over scrolling on a expanded card.
178 */
179 private float mMinTopOverScrollToEscape;
180 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700181 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200182 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200183 private boolean mDontReportNextOverScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200184 private boolean mRequestViewResizeAnimationOnLayout;
185 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100186 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700187 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700188
Selim Cinek1408eb52014-06-02 14:45:38 +0200189 /**
190 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
191 * This is needed to avoid scrolling too far after the notification was collapsed in the same
192 * motion.
193 */
194 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400195 private SwipeHelper.LongPressListener mLongPressListener;
Selim Cinek1408eb52014-06-02 14:45:38 +0200196
197 /**
198 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
199 * animating.
200 */
201 private boolean mOnlyScrollingInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200202 private ViewGroup mScrollView;
203 private boolean mInterceptDelegateEnabled;
204 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700205 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700206 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200207 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200208 = new ViewTreeObserver.OnPreDrawListener() {
209 @Override
210 public boolean onPreDraw() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200211 updateChildren();
212 mChildrenUpdateRequested = false;
213 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200214 return true;
215 }
216 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200217 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100218 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100219 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700220 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek0fccc722015-07-29 17:04:36 -0700221 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700222 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
223 = new HashSet<>();
224 private HeadsUpManager mHeadsUpManager;
225 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700226 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200227 private boolean mForceNoOverlappingRendering;
Selim Cinek2cd45df2015-06-09 18:00:07 -0700228 private NotificationOverflowContainer mOverflowContainer;
Selim Cineke0890e52015-06-17 11:17:08 -0700229 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700230 private FalsingManager mFalsingManager;
Selim Cinek67b22602014-03-10 15:40:16 +0100231
232 public NotificationStackScrollLayout(Context context) {
233 this(context, null);
234 }
235
236 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
237 this(context, attrs, 0);
238 }
239
240 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
241 this(context, attrs, defStyleAttr, 0);
242 }
243
244 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
245 int defStyleRes) {
246 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200247 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
248 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
249 mExpandHelper = new ExpandHelper(getContext(), this,
250 minHeight, maxHeight);
251 mExpandHelper.setEventSource(this);
252 mExpandHelper.setScrollAdapter(this);
253
254 mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
255 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800256 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100257 initView(context);
258 if (DEBUG) {
259 setWillNotDraw(false);
260 mDebugPaint = new Paint();
261 mDebugPaint.setColor(0xffff0000);
262 mDebugPaint.setStrokeWidth(2);
263 mDebugPaint.setStyle(Paint.Style.STROKE);
264 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700265 mFalsingManager = FalsingManager.getInstance(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100266 }
267
268 @Override
269 protected void onDraw(Canvas canvas) {
270 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800271 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100272 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200273 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200274 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200275 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
276 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100277 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
278 y = (int) getLayoutHeight();
279 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200280 y = getHeight() - getEmptyBottomMargin();
281 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100282 }
283 }
284
285 private void initView(Context context) {
286 mScroller = new OverScroller(getContext());
287 setFocusable(true);
288 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200289 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100290 final ViewConfiguration configuration = ViewConfiguration.get(context);
291 mTouchSlop = configuration.getScaledTouchSlop();
292 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
293 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100294 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100295 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200296 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100297 mBottomStackPeekSize = context.getResources()
298 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800299 mStackScrollAlgorithm.initView(context);
Selim Cineka5eaa602014-05-12 21:27:47 +0200300 mPaddingBetweenElementsNormal = context.getResources()
301 .getDimensionPixelSize(R.dimen.notification_padding);
Selim Cinek587cbf32016-01-19 11:36:18 -0800302 mPaddingBetweenElements = mPaddingBetweenElementsNormal;
303 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
Selim Cinek1408eb52014-06-02 14:45:38 +0200304 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
305 R.dimen.min_top_overscroll_to_qs);
Selim Cinekd83771e2014-07-04 16:45:31 +0200306 mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
307 R.dimen.notification_collapse_second_card_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200308 }
309
Selim Cinekaef92ef2014-06-06 18:06:04 +0200310 private void notifyHeightChangeListener(ExpandableView view) {
311 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100312 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200313 }
Selim Cinek67b22602014-03-10 15:40:16 +0100314 }
315
316 @Override
317 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
318 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinek924c6122016-01-15 18:24:05 -0800319 measureChildren(widthMeasureSpec, heightMeasureSpec);
Selim Cinek67b22602014-03-10 15:40:16 +0100320 }
321
322 @Override
323 protected void onLayout(boolean changed, int l, int t, int r, int b) {
324
325 // we layout all our children centered on the top
326 float centerX = getWidth() / 2.0f;
327 for (int i = 0; i < getChildCount(); i++) {
328 View child = getChildAt(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100329 if (child.getVisibility() == GONE) {
330 continue;
331 }
Selim Cinek67b22602014-03-10 15:40:16 +0100332 float width = child.getMeasuredWidth();
333 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100334 child.layout((int) (centerX - width / 2.0f),
335 0,
336 (int) (centerX + width / 2.0f),
337 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100338 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200339 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100340 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200341 clampScrollPosition();
Selim Cinekb5605e52015-02-20 18:21:41 +0100342 if (mRequestViewResizeAnimationOnLayout) {
Selim Cinek5bc852a2015-12-21 12:19:09 -0800343 requestAnimationOnViewResize(null);
Selim Cinekb5605e52015-02-20 18:21:41 +0100344 mRequestViewResizeAnimationOnLayout = false;
345 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200346 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100347 }
348
Selim Cinek5bc852a2015-12-21 12:19:09 -0800349 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
350 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200351 mNeedViewResizeAnimation = true;
352 mNeedsAnimation = true;
353 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200354 }
355
Selim Cinekc27437b2014-05-14 10:23:33 +0200356 public void updateSpeedBumpIndex(int newIndex) {
Selim Cinekc27437b2014-05-14 10:23:33 +0200357 mAmbientState.setSpeedBumpIndex(newIndex);
358 }
359
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200360 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
361 mListener = listener;
362 }
363
364 /**
365 * Returns the location the given child is currently rendered at.
366 *
367 * @param child the child to get the location for
Selim Cinekb036ca42015-02-20 15:56:28 +0100368 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200369 */
370 public int getChildLocation(View child) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100371 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200372 if (childViewState == null) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100373 return StackViewState.LOCATION_UNKNOWN;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200374 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100375 if (childViewState.gone) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100376 return StackViewState.LOCATION_GONE;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100377 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200378 return childViewState.location;
379 }
380
Selim Cinek67b22602014-03-10 15:40:16 +0100381 private void setMaxLayoutHeight(int maxLayoutHeight) {
382 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200383 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100384 }
385
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200386 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700387 mAmbientState.setLayoutHeight(getLayoutHeight());
388 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100389 }
390
391 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +0200392 * @return whether the height of the layout needs to be adapted, in order to ensure that the
393 * last child is not in the bottom stack.
394 */
395 private boolean needsHeightAdaption() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200396 return getNotGoneChildCount() > 1;
Selim Cinek4a1ac842014-05-01 15:51:58 +0200397 }
398
Selim Cinek4a1ac842014-05-01 15:51:58 +0200399 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100400 * Updates the children views according to the stack scroll algorithm. Call this whenever
401 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
402 */
403 private void updateChildren() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200404 mAmbientState.setScrollY(mOwnScrollY);
405 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200406 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200407 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100408 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200409 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100410 }
411 }
412
Selim Cinek319bdc42014-05-01 23:01:58 +0200413 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200414 if (!mChildrenUpdateRequested) {
415 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
416 mChildrenUpdateRequested = true;
417 invalidate();
418 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200419 }
420
Selim Cinek67b22602014-03-10 15:40:16 +0100421 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200422 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100423 }
424
Selim Cinekf7a14c02014-07-07 14:01:46 +0200425 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100426 int scrollRange = getScrollRange();
427 if (scrollRange < mOwnScrollY) {
428 mOwnScrollY = scrollRange;
429 }
430 }
431
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200432 public int getTopPadding() {
433 return mTopPadding;
434 }
435
Selim Cinek1408eb52014-06-02 14:45:38 +0200436 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200437 if (mTopPadding != topPadding) {
438 mTopPadding = topPadding;
439 updateAlgorithmHeightAndPadding();
440 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200441 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200442 mTopPaddingNeedsAnimation = true;
443 mNeedsAnimation = true;
444 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200445 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200446 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200447 }
448 }
449
450 /**
451 * Update the height of the stack to a new height.
452 *
453 * @param height the new height of the stack
454 */
455 public void setStackHeight(float height) {
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100456 mLastSetStackHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200457 setIsExpanded(height > 0.0f);
458 int newStackHeight = (int) height;
Selim Cinekd83771e2014-07-04 16:45:31 +0200459 int minStackHeight = getMinStackHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200460 int stackHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700461 float paddingOffset;
Selim Cinek131c1e22015-05-11 19:04:49 -0700462 boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
Selim Cinek31aada42015-12-18 17:51:15 -0800463 int normalUnfoldPositionStart = trackingHeadsUp
464 ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700465 : minStackHeight;
Selim Cinek684a4422015-04-15 16:18:39 -0700466 if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
Jorim Jaggi58bef332014-11-21 18:10:32 +0100467 || getNotGoneChildCount() == 0) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700468 paddingOffset = mTopPaddingOverflow;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200469 stackHeight = newStackHeight;
470 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800471 int translationY;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700472 if (!trackingHeadsUp) {
Selim Cinek31aada42015-12-18 17:51:15 -0800473 // We did not reach the position yet where we actually start growing,
474 // so we translate the stack upwards.
475 translationY = (newStackHeight - minStackHeight);
476 // A slight parallax effect is introduced in order for the stack to catch up with
477 // the top card.
478 float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
479 / minStackHeight;
480 partiallyThere = Math.max(0, partiallyThere);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700481 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
482 mCollapseSecondCardPadding);
483 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800484 translationY = (int) (height - normalUnfoldPositionStart);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700485 }
486 paddingOffset = translationY - mTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200487 stackHeight = (int) (height - (translationY - mTopPadding));
488 }
489 if (stackHeight != mCurrentStackHeight) {
490 mCurrentStackHeight = stackHeight;
491 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200492 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200493 }
Selim Cinekd2281152015-04-10 14:37:46 -0700494 setStackTranslation(paddingOffset);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700495 }
496
Selim Cinekd2281152015-04-10 14:37:46 -0700497 public float getStackTranslation() {
498 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700499 }
500
Selim Cinekd2281152015-04-10 14:37:46 -0700501 private void setStackTranslation(float stackTranslation) {
502 if (stackTranslation != mStackTranslation) {
503 mStackTranslation = stackTranslation;
504 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700505 requestChildrenUpdate();
506 }
Selim Cinek67b22602014-03-10 15:40:16 +0100507 }
508
509 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100510 * 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 +0100511 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
512 *
513 * @return either the layout height or the externally defined height, whichever is smaller
514 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200515 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100516 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
517 }
518
Selim Cinek816c8e42015-11-19 12:00:45 -0800519 public int getFirstItemMinHeight() {
520 final ExpandableView firstChild = getFirstChildNotGone();
521 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100522 }
523
524 public int getBottomStackPeekSize() {
525 return mBottomStackPeekSize;
526 }
527
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200528 public int getCollapseSecondCardPadding() {
529 return mCollapseSecondCardPadding;
530 }
531
Dan Sandler4247a5c2014-07-23 15:58:08 -0400532 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100533 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400534 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100535 }
536
Jorim Jaggi56306252014-07-03 00:40:09 +0200537 public void setScrollView(ViewGroup scrollView) {
538 mScrollView = scrollView;
539 }
540
541 public void setInterceptDelegateEnabled(boolean interceptDelegateEnabled) {
542 mInterceptDelegateEnabled = interceptDelegateEnabled;
543 }
544
Selim Cinek67b22602014-03-10 15:40:16 +0100545 public void onChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400546 if (mDismissAllInProgress) {
547 return;
548 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100549 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200550 if (mDragAnimPendingChildren.contains(v)) {
551 // We start the swipe and finish it in the same frame, we don't want any animation
552 // for the drag
553 mDragAnimPendingChildren.remove(v);
554 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200555 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200556 mAmbientState.onDragFinished(v);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700557 if (v instanceof ExpandableNotificationRow) {
558 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
559 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700560 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700561 }
562 }
563 final View veto = v.findViewById(R.id.veto);
564 if (veto != null && veto.getVisibility() != View.GONE) {
565 veto.performClick();
566 }
567 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
Blazej Magnowski72323322015-07-24 11:49:40 -0700568
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700569 mFalsingManager.onNotificationDismissed();
570 if (mFalsingManager.shouldEnforceBouncer()) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700571 mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
572 false /* dismissShade */, true /* afterKeyguardGone */);
573 }
Selim Cinekeb973562014-05-02 17:07:49 +0200574 }
575
576 @Override
577 public void onChildSnappedBack(View animView) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200578 mAmbientState.onDragFinished(animView);
Selim Cinekeb973562014-05-02 17:07:49 +0200579 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200580 if (mAnimationsEnabled) {
581 mSnappedBackChildren.add(animView);
582 mNeedsAnimation = true;
583 }
Selim Cinekeb973562014-05-02 17:07:49 +0200584 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200585 } else {
586 // We start the swipe and snap back in the same frame, we don't want any animation
587 mDragAnimPendingChildren.remove(animView);
588 }
Selim Cinek67b22602014-03-10 15:40:16 +0100589 }
590
Adrian Roos5d9cc662014-05-28 17:08:13 +0200591 @Override
592 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700593 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700594 mScrimController.setTopHeadsUpDragAmount(animView,
595 Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
596 }
Adrian Roos5d9cc662014-05-28 17:08:13 +0200597 return false;
598 }
599
Selim Cinek67b22602014-03-10 15:40:16 +0100600 public void onBeginDrag(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700601 mFalsingManager.onNotificatonStartDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100602 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200603 mAmbientState.onBeginDrag(v);
Selim Cinek131c1e22015-05-11 19:04:49 -0700604 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200605 mDragAnimPendingChildren.add(v);
606 mNeedsAnimation = true;
607 }
Selim Cinekeb973562014-05-02 17:07:49 +0200608 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100609 }
610
Selim Cinek684a4422015-04-15 16:18:39 -0700611 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700612 if (v instanceof ExpandableNotificationRow) {
613 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700614 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700615 }
616 return false;
617 }
618
619 private boolean isHeadsUp(View v) {
620 if (v instanceof ExpandableNotificationRow) {
621 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
622 return row.isHeadsUp();
623 }
624 return false;
625 }
626
Selim Cinek67b22602014-03-10 15:40:16 +0100627 public void onDragCancelled(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700628 mFalsingManager.onNotificatonStopDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100629 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100630 }
631
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700632 @Override
633 public float getFalsingThresholdFactor() {
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700634 return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700635 }
636
Selim Cinek67b22602014-03-10 15:40:16 +0100637 public View getChildAtPosition(MotionEvent ev) {
638 return getChildAtPosition(ev.getX(), ev.getY());
639 }
640
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100641 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
642 getLocationOnScreen(mTempInt2);
643 float localTouchY = touchY - mTempInt2[1];
644
645 ExpandableView closestChild = null;
646 float minDist = Float.MAX_VALUE;
647
648 // find the view closest to the location, accounting for GONE views
649 final int count = getChildCount();
650 for (int childIdx = 0; childIdx < count; childIdx++) {
651 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
652 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800653 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100654 continue;
655 }
656 float childTop = slidingChild.getTranslationY();
657 float top = childTop + slidingChild.getClipTopAmount();
658 float bottom = childTop + slidingChild.getActualHeight();
659
660 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
661 if (dist < minDist) {
662 closestChild = slidingChild;
663 minDist = dist;
664 }
665 }
666 return closestChild;
667 }
668
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200669 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100670 getLocationOnScreen(mTempInt2);
671 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100672 }
673
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200674 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100675 // find the view under the pointer, accounting for GONE views
676 final int count = getChildCount();
677 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200678 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100679 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800680 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100681 continue;
682 }
Selim Cinek89faff12014-06-19 16:29:04 -0700683 float childTop = slidingChild.getTranslationY();
684 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200685 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200686
687 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
688 // camera affordance).
689 int left = 0;
690 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100691
692 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100693 if (slidingChild instanceof ExpandableNotificationRow) {
694 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -0700695 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cinek5bc852a2015-12-21 12:19:09 -0800696 && mHeadsUpManager.getTopEntry().entry.row != row
697 && mGroupManager.getGroupSummary(
698 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
699 != row) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700700 continue;
701 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100702 return row.getViewAtPosition(touchY - childTop);
703 }
Selim Cinek67b22602014-03-10 15:40:16 +0100704 return slidingChild;
705 }
706 }
707 return null;
708 }
709
710 public boolean canChildBeExpanded(View v) {
711 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -0700712 && ((ExpandableNotificationRow) v).isExpandable()
713 && !((ExpandableNotificationRow) v).isHeadsUp();
Selim Cinek67b22602014-03-10 15:40:16 +0100714 }
715
716 public void setUserExpandedChild(View v, boolean userExpanded) {
717 if (v instanceof ExpandableNotificationRow) {
Selim Cinek388df6d2015-10-22 13:25:11 -0700718 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded,
719 true /* allowChildrenExpansion */);
Selim Cinek67b22602014-03-10 15:40:16 +0100720 }
721 }
722
723 public void setUserLockedChild(View v, boolean userLocked) {
724 if (v instanceof ExpandableNotificationRow) {
725 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
726 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200727 removeLongPressCallback();
728 requestDisallowInterceptTouchEvent(true);
729 }
730
731 @Override
732 public void expansionStateChanged(boolean isExpanding) {
733 mExpandingNotification = isExpanding;
734 if (!mExpandedInThisMotion) {
735 mMaxScrollAfterExpand = mOwnScrollY;
736 mExpandedInThisMotion = true;
737 }
738 }
739
740 public void setScrollingEnabled(boolean enable) {
741 mScrollingEnabled = enable;
742 }
743
744 public void setExpandingEnabled(boolean enable) {
745 mExpandHelper.setEnabled(enable);
746 }
747
748 private boolean isScrollingEnabled() {
749 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100750 }
751
752 public View getChildContentView(View v) {
753 return v;
754 }
755
756 public boolean canChildBeDismissed(View v) {
Selim Cinek9c17b772015-07-07 20:37:09 -0700757 return StackScrollAlgorithm.canChildBeDismissed(v);
Selim Cinek67b22602014-03-10 15:40:16 +0100758 }
759
Selim Cinek19c8c702014-08-25 22:09:19 +0200760 @Override
761 public boolean isAntiFalsingNeeded() {
Selim Cinekcb2b6732014-09-05 16:17:22 +0200762 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +0200763 }
764
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100765 private void setSwipingInProgress(boolean isSwiped) {
766 mSwipingInProgress = isSwiped;
767 if(isSwiped) {
768 requestDisallowInterceptTouchEvent(true);
769 }
Selim Cinek67b22602014-03-10 15:40:16 +0100770 }
771
772 @Override
773 protected void onConfigurationChanged(Configuration newConfig) {
774 super.onConfigurationChanged(newConfig);
775 float densityScale = getResources().getDisplayMetrics().density;
776 mSwipeHelper.setDensityScale(densityScale);
777 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
778 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
779 initView(getContext());
780 }
781
Dan Sandlereceda3d2014-07-21 15:35:01 -0400782 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400783 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
Selim Cinek67b22602014-03-10 15:40:16 +0100784 }
785
786 @Override
787 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200788 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
789 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Jorim Jaggi56306252014-07-03 00:40:09 +0200790 if (mDelegateToScrollView) {
791 if (isCancelOrUp) {
792 mDelegateToScrollView = false;
793 }
794 transformTouchEvent(ev, this, mScrollView);
795 return mScrollView.onTouchEvent(ev);
796 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100797 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +0200798 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +0200799 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200800 if (isCancelOrUp) {
801 mExpandHelper.onlyObserveMovements(false);
802 }
803 boolean wasExpandingBefore = mExpandingNotification;
804 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +0200805 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
806 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200807 dispatchDownEventToScroller(ev);
808 }
809 }
Selim Cinek67b22602014-03-10 15:40:16 +0100810 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700811 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
812 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100813 scrollerWantsIt = onScrollTouch(ev);
814 }
815 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200816 if (!mIsBeingDragged
817 && !mExpandingNotification
818 && !mExpandedInThisMotion
819 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100820 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
821 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200822 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
823 }
824
825 private void dispatchDownEventToScroller(MotionEvent ev) {
826 MotionEvent downEvent = MotionEvent.obtain(ev);
827 downEvent.setAction(MotionEvent.ACTION_DOWN);
828 onScrollTouch(downEvent);
829 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +0100830 }
831
832 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200833 if (!isScrollingEnabled()) {
834 return false;
835 }
Selim Cinek67b22602014-03-10 15:40:16 +0100836 initVelocityTrackerIfNotExists();
837 mVelocityTracker.addMovement(ev);
838
839 final int action = ev.getAction();
840
841 switch (action & MotionEvent.ACTION_MASK) {
842 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +0200843 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +0100844 return false;
845 }
846 boolean isBeingDragged = !mScroller.isFinished();
847 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +0100848
849 /*
850 * If being flinged and user touches, stop the fling. isFinished
851 * will be false if being flinged.
852 */
853 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200854 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +0100855 }
856
857 // Remember where the motion event started
858 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +0200859 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +0100860 mActivePointerId = ev.getPointerId(0);
861 break;
862 }
863 case MotionEvent.ACTION_MOVE:
864 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
865 if (activePointerIndex == -1) {
866 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
867 break;
868 }
869
870 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +0200871 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +0100872 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200873 final int xDiff = Math.abs(x - mDownX);
874 final int yDiff = Math.abs(deltaY);
875 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +0100876 setIsBeingDragged(true);
877 if (deltaY > 0) {
878 deltaY -= mTouchSlop;
879 } else {
880 deltaY += mTouchSlop;
881 }
882 }
883 if (mIsBeingDragged) {
884 // Scroll to follow the motion event
885 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200886 int range = getScrollRange();
887 if (mExpandedInThisMotion) {
888 range = Math.min(range, mMaxScrollAfterExpand);
889 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200890
891 float scrollAmount;
892 if (deltaY < 0) {
893 scrollAmount = overScrollDown(deltaY);
894 } else {
895 scrollAmount = overScrollUp(deltaY, range);
896 }
Selim Cinek67b22602014-03-10 15:40:16 +0100897
898 // Calling overScrollBy will call onOverScrolled, which
899 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200900 if (scrollAmount != 0.0f) {
901 // The scrolling motion could not be compensated with the
902 // existing overScroll, we have to scroll the view
903 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
904 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +0100905 }
Selim Cinek67b22602014-03-10 15:40:16 +0100906 }
907 break;
908 case MotionEvent.ACTION_UP:
909 if (mIsBeingDragged) {
910 final VelocityTracker velocityTracker = mVelocityTracker;
911 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
912 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
913
Selim Cinek1408eb52014-06-02 14:45:38 +0200914 if (shouldOverScrollFling(initialVelocity)) {
915 onOverScrollFling(true, initialVelocity);
916 } else {
917 if (getChildCount() > 0) {
918 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
919 float currentOverScrollTop = getCurrentOverScrollAmount(true);
920 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
921 fling(-initialVelocity);
922 } else {
923 onOverScrollFling(false, initialVelocity);
924 }
925 } else {
926 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
927 getScrollRange())) {
928 postInvalidateOnAnimation();
929 }
Selim Cinek67b22602014-03-10 15:40:16 +0100930 }
931 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200932 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700933
934 mActivePointerId = INVALID_POINTER;
935 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +0100936 }
Selim Cinek48e746c2014-06-16 16:01:03 -0700937
Selim Cinek67b22602014-03-10 15:40:16 +0100938 break;
939 case MotionEvent.ACTION_CANCEL:
940 if (mIsBeingDragged && getChildCount() > 0) {
941 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
942 postInvalidateOnAnimation();
943 }
944 mActivePointerId = INVALID_POINTER;
945 endDrag();
946 }
947 break;
948 case MotionEvent.ACTION_POINTER_DOWN: {
949 final int index = ev.getActionIndex();
950 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +0200951 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +0100952 mActivePointerId = ev.getPointerId(index);
953 break;
954 }
955 case MotionEvent.ACTION_POINTER_UP:
956 onSecondaryPointerUp(ev);
957 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +0200958 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +0100959 break;
960 }
961 return true;
962 }
963
Selim Cinek1408eb52014-06-02 14:45:38 +0200964 private void onOverScrollFling(boolean open, int initialVelocity) {
965 if (mOverscrollTopChangedListener != null) {
966 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
967 }
968 mDontReportNextOverScroll = true;
969 setOverScrollAmount(0.0f, true, false);
970 }
971
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200972 /**
973 * Perform a scroll upwards and adapt the overscroll amounts accordingly
974 *
975 * @param deltaY The amount to scroll upwards, has to be positive.
976 * @return The amount of scrolling to be performed by the scroller,
977 * not handled by the overScroll amount.
978 */
979 private float overScrollUp(int deltaY, int range) {
980 deltaY = Math.max(deltaY, 0);
981 float currentTopAmount = getCurrentOverScrollAmount(true);
982 float newTopAmount = currentTopAmount - deltaY;
983 if (currentTopAmount > 0) {
984 setOverScrollAmount(newTopAmount, true /* onTop */,
985 false /* animate */);
986 }
987 // Top overScroll might not grab all scrolling motion,
988 // we have to scroll as well.
989 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
990 float newScrollY = mOwnScrollY + scrollAmount;
991 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200992 if (!mExpandedInThisMotion) {
993 float currentBottomPixels = getCurrentOverScrolledPixels(false);
994 // We overScroll on the top
995 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
996 false /* onTop */,
997 false /* animate */);
998 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200999 mOwnScrollY = range;
1000 scrollAmount = 0.0f;
1001 }
1002 return scrollAmount;
1003 }
1004
1005 /**
1006 * Perform a scroll downward and adapt the overscroll amounts accordingly
1007 *
1008 * @param deltaY The amount to scroll downwards, has to be negative.
1009 * @return The amount of scrolling to be performed by the scroller,
1010 * not handled by the overScroll amount.
1011 */
1012 private float overScrollDown(int deltaY) {
1013 deltaY = Math.min(deltaY, 0);
1014 float currentBottomAmount = getCurrentOverScrollAmount(false);
1015 float newBottomAmount = currentBottomAmount + deltaY;
1016 if (currentBottomAmount > 0) {
1017 setOverScrollAmount(newBottomAmount, false /* onTop */,
1018 false /* animate */);
1019 }
1020 // Bottom overScroll might not grab all scrolling motion,
1021 // we have to scroll as well.
1022 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1023 float newScrollY = mOwnScrollY + scrollAmount;
1024 if (newScrollY < 0) {
1025 float currentTopPixels = getCurrentOverScrolledPixels(true);
1026 // We overScroll on the top
1027 setOverScrolledPixels(currentTopPixels - newScrollY,
1028 true /* onTop */,
1029 false /* animate */);
1030 mOwnScrollY = 0;
1031 scrollAmount = 0.0f;
1032 }
1033 return scrollAmount;
1034 }
1035
Selim Cinek67b22602014-03-10 15:40:16 +01001036 private void onSecondaryPointerUp(MotionEvent ev) {
1037 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1038 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1039 final int pointerId = ev.getPointerId(pointerIndex);
1040 if (pointerId == mActivePointerId) {
1041 // This was our active pointer going up. Choose a new
1042 // active pointer and adjust accordingly.
1043 // TODO: Make this decision more intelligent.
1044 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1045 mLastMotionY = (int) ev.getY(newPointerIndex);
1046 mActivePointerId = ev.getPointerId(newPointerIndex);
1047 if (mVelocityTracker != null) {
1048 mVelocityTracker.clear();
1049 }
1050 }
1051 }
1052
1053 private void initVelocityTrackerIfNotExists() {
1054 if (mVelocityTracker == null) {
1055 mVelocityTracker = VelocityTracker.obtain();
1056 }
1057 }
1058
1059 private void recycleVelocityTracker() {
1060 if (mVelocityTracker != null) {
1061 mVelocityTracker.recycle();
1062 mVelocityTracker = null;
1063 }
1064 }
1065
1066 private void initOrResetVelocityTracker() {
1067 if (mVelocityTracker == null) {
1068 mVelocityTracker = VelocityTracker.obtain();
1069 } else {
1070 mVelocityTracker.clear();
1071 }
1072 }
1073
1074 @Override
1075 public void computeScroll() {
1076 if (mScroller.computeScrollOffset()) {
1077 // This is called at drawing time by ViewGroup.
1078 int oldX = mScrollX;
1079 int oldY = mOwnScrollY;
1080 int x = mScroller.getCurrX();
1081 int y = mScroller.getCurrY();
1082
1083 if (oldX != x || oldY != y) {
1084 final int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001085 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1086 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001087 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001088 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1089 }
1090 }
Selim Cinek67b22602014-03-10 15:40:16 +01001091
1092 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001093 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001094 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001095 }
1096
1097 // Keep on drawing until the animation has finished.
1098 postInvalidateOnAnimation();
1099 }
1100 }
1101
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001102 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001103 protected boolean overScrollBy(int deltaX, int deltaY,
1104 int scrollX, int scrollY,
1105 int scrollRangeX, int scrollRangeY,
1106 int maxOverScrollX, int maxOverScrollY,
1107 boolean isTouchEvent) {
1108
1109 int newScrollY = scrollY + deltaY;
1110
1111 final int top = -maxOverScrollY;
1112 final int bottom = maxOverScrollY + scrollRangeY;
1113
1114 boolean clampedY = false;
1115 if (newScrollY > bottom) {
1116 newScrollY = bottom;
1117 clampedY = true;
1118 } else if (newScrollY < top) {
1119 newScrollY = top;
1120 clampedY = true;
1121 }
1122
1123 onOverScrolled(0, newScrollY, false, clampedY);
1124
1125 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001126 }
1127
1128 /**
1129 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1130 * overscroll effect based on numPixels. By default this will also cancel animations on the
1131 * same overScroll edge.
1132 *
1133 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1134 * the rubber-banding logic.
1135 * @param onTop Should the effect be applied on top of the scroller.
1136 * @param animate Should an animation be performed.
1137 */
1138 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001139 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001140 }
1141
1142 /**
1143 * Set the effective overScroll amount which will be directly reflected in the layout.
1144 * By default this will also cancel animations on the same overScroll edge.
1145 *
1146 * @param amount The amount to overScroll by.
1147 * @param onTop Should the effect be applied on top of the scroller.
1148 * @param animate Should an animation be performed.
1149 */
1150 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1151 setOverScrollAmount(amount, onTop, animate, true);
1152 }
1153
1154 /**
1155 * Set the effective overScroll amount which will be directly reflected in the layout.
1156 *
1157 * @param amount The amount to overScroll by.
1158 * @param onTop Should the effect be applied on top of the scroller.
1159 * @param animate Should an animation be performed.
1160 * @param cancelAnimators Should running animations be cancelled.
1161 */
1162 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1163 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001164 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1165 }
1166
1167 /**
1168 * Set the effective overScroll amount which will be directly reflected in the layout.
1169 *
1170 * @param amount The amount to overScroll by.
1171 * @param onTop Should the effect be applied on top of the scroller.
1172 * @param animate Should an animation be performed.
1173 * @param cancelAnimators Should running animations be cancelled.
1174 * @param isRubberbanded The value which will be passed to
1175 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1176 */
1177 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1178 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001179 if (cancelAnimators) {
1180 mStateAnimator.cancelOverScrollAnimators(onTop);
1181 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001182 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001183 }
1184
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001185 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1186 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001187 amount = Math.max(0, amount);
1188 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001189 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001190 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001191 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001192 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001193 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001194 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001195 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001196 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001197 }
1198 }
1199
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001200 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001201 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1202 if (mDontReportNextOverScroll) {
1203 mDontReportNextOverScroll = false;
1204 return;
1205 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001206 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001207 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001208 }
1209 }
1210
1211 public void setOverscrollTopChangedListener(
1212 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1213 mOverscrollTopChangedListener = overscrollTopChangedListener;
1214 }
1215
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001216 public float getCurrentOverScrollAmount(boolean top) {
1217 return mAmbientState.getOverScrollAmount(top);
1218 }
1219
1220 public float getCurrentOverScrolledPixels(boolean top) {
1221 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1222 }
1223
1224 private void setOverScrolledPixels(float amount, boolean onTop) {
1225 if (onTop) {
1226 mOverScrolledTopPixels = amount;
1227 } else {
1228 mOverScrolledBottomPixels = amount;
1229 }
1230 }
1231
Selim Cinek319bdc42014-05-01 23:01:58 +02001232 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001233 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001234 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001235 }
1236
1237 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001238 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001239 // Treat animating scrolls differently; see #computeScroll() for why.
1240 if (!mScroller.isFinished()) {
1241 final int oldX = mScrollX;
1242 final int oldY = mOwnScrollY;
1243 mScrollX = scrollX;
1244 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001245 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001246 springBack();
1247 } else {
1248 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1249 invalidateParentIfNeeded();
1250 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001251 float overScrollTop = getCurrentOverScrollAmount(true);
1252 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001253 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001254 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001255 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001256 }
Selim Cinek67b22602014-03-10 15:40:16 +01001257 }
Selim Cinek67b22602014-03-10 15:40:16 +01001258 } else {
1259 customScrollTo(scrollY);
1260 scrollTo(scrollX, mScrollY);
1261 }
1262 }
1263
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001264 private void springBack() {
1265 int scrollRange = getScrollRange();
1266 boolean overScrolledTop = mOwnScrollY <= 0;
1267 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1268 if (overScrolledTop || overScrolledBottom) {
1269 boolean onTop;
1270 float newAmount;
1271 if (overScrolledTop) {
1272 onTop = true;
1273 newAmount = -mOwnScrollY;
1274 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001275 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001276 } else {
1277 onTop = false;
1278 newAmount = mOwnScrollY - scrollRange;
1279 mOwnScrollY = scrollRange;
1280 }
1281 setOverScrollAmount(newAmount, onTop, false);
1282 setOverScrollAmount(0.0f, onTop, true);
1283 mScroller.forceFinished(true);
1284 }
1285 }
1286
Selim Cinek67b22602014-03-10 15:40:16 +01001287 private int getScrollRange() {
1288 int scrollRange = 0;
Jorim Jaggibe565df2014-04-28 17:51:23 +02001289 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001290 if (firstChild != null) {
Selim Cinek67b22602014-03-10 15:40:16 +01001291 int contentHeight = getContentHeight();
Selim Cineka5eaa602014-05-12 21:27:47 +02001292 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1293 + mBottomStackSlowDownHeight);
Selim Cinek4a1ac842014-05-01 15:51:58 +02001294 if (scrollRange > 0) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001295 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
Selim Cinek343e6e22014-04-11 21:23:30 +02001296 // We want to at least be able collapse the first item and not ending in a weird
1297 // end state.
Selim Cinek816c8e42015-11-19 12:00:45 -08001298 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight
1299 - firstChild.getMinHeight());
Selim Cinek343e6e22014-04-11 21:23:30 +02001300 }
Selim Cinek67b22602014-03-10 15:40:16 +01001301 }
1302 return scrollRange;
1303 }
1304
Selim Cinek343e6e22014-04-11 21:23:30 +02001305 /**
1306 * @return the first child which has visibility unequal to GONE
1307 */
Selim Cinekb55386d2015-12-16 17:26:49 -08001308 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02001309 int childCount = getChildCount();
1310 for (int i = 0; i < childCount; i++) {
1311 View child = getChildAt(i);
1312 if (child.getVisibility() != View.GONE) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001313 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02001314 }
1315 }
1316 return null;
1317 }
1318
Selim Cinek4a1ac842014-05-01 15:51:58 +02001319 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001320 * @return The first child which has visibility unequal to GONE which is currently below the
1321 * given translationY or equal to it.
1322 */
1323 private View getFirstChildBelowTranlsationY(float translationY) {
1324 int childCount = getChildCount();
1325 for (int i = 0; i < childCount; i++) {
1326 View child = getChildAt(i);
1327 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1328 return child;
1329 }
1330 }
1331 return null;
1332 }
1333
1334 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001335 * @return the last child which has visibility unequal to GONE
1336 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001337 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001338 int childCount = getChildCount();
1339 for (int i = childCount - 1; i >= 0; i--) {
1340 View child = getChildAt(i);
1341 if (child.getVisibility() != View.GONE) {
1342 return child;
1343 }
1344 }
1345 return null;
1346 }
1347
Jorim Jaggi069cd032014-05-15 03:09:01 +02001348 /**
1349 * @return the number of children which have visibility unequal to GONE
1350 */
1351 public int getNotGoneChildCount() {
1352 int childCount = getChildCount();
1353 int count = 0;
1354 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07001355 ExpandableView child = (ExpandableView) getChildAt(i);
1356 if (child.getVisibility() != View.GONE && !child.willBeGone()) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02001357 count++;
1358 }
1359 }
1360 return count;
1361 }
1362
Selim Cinek343e6e22014-04-11 21:23:30 +02001363 private int getMaxExpandHeight(View view) {
1364 if (view instanceof ExpandableNotificationRow) {
1365 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001366 return row.getIntrinsicHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001367 }
1368 return view.getHeight();
1369 }
1370
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001371 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001372 return mContentHeight;
1373 }
1374
1375 private void updateContentHeight() {
1376 int height = 0;
1377 for (int i = 0; i < getChildCount(); i++) {
1378 View child = getChildAt(i);
Jorim Jaggid4a57442014-04-10 02:45:55 +02001379 if (child.getVisibility() != View.GONE) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001380 if (height != 0) {
1381 // add the padding before this element
Jorim Jaggid4a57442014-04-10 02:45:55 +02001382 height += mPaddingBetweenElements;
1383 }
Selim Cineke53e6bb2015-04-13 16:14:26 -07001384 if (child instanceof ExpandableView) {
Jorim Jaggibe565df2014-04-28 17:51:23 +02001385 ExpandableView expandableView = (ExpandableView) child;
Selim Cineke53e6bb2015-04-13 16:14:26 -07001386 height += expandableView.getIntrinsicHeight();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001387 }
Selim Cinek67b22602014-03-10 15:40:16 +01001388 }
1389 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001390 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001391 }
1392
1393 /**
1394 * Fling the scroll view
1395 *
1396 * @param velocityY The initial velocity in the Y direction. Positive
1397 * numbers mean that the finger/cursor is moving down the screen,
1398 * which means we want to scroll towards the top.
1399 */
1400 private void fling(int velocityY) {
1401 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001402 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001403
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001404 float topAmount = getCurrentOverScrollAmount(true);
1405 float bottomAmount = getCurrentOverScrollAmount(false);
1406 if (velocityY < 0 && topAmount > 0) {
1407 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001408 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001409 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001410 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001411 * mOverflingDistance + topAmount;
1412 } else if (velocityY > 0 && bottomAmount > 0) {
1413 mOwnScrollY += bottomAmount;
1414 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001415 mMaxOverScroll = Math.abs(velocityY) / 1000f
1416 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1417 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001418 } else {
1419 // it will be set once we reach the boundary
1420 mMaxOverScroll = 0.0f;
1421 }
1422 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek4195dd02014-05-19 18:16:14 +02001423 Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01001424
1425 postInvalidateOnAnimation();
1426 }
1427 }
1428
Selim Cinek1408eb52014-06-02 14:45:38 +02001429 /**
1430 * @return Whether a fling performed on the top overscroll edge lead to the expanded
1431 * overScroll view (i.e QS).
1432 */
1433 private boolean shouldOverScrollFling(int initialVelocity) {
1434 float topOverScroll = getCurrentOverScrollAmount(true);
1435 return mScrolledToTopOnFirstDown
1436 && !mExpandedInThisMotion
1437 && topOverScroll > mMinTopOverScrollToEscape
1438 && initialVelocity > 0;
1439 }
1440
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001441 /**
1442 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1443 * account.
1444 *
1445 * @param qsHeight the top padding imposed by the quick settings panel
1446 * @param scrollY how much the notifications are scrolled inside the QS/notifications scroll
1447 * container
1448 * @param animate whether to animate the change
1449 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1450 * {@code qsHeight} is the final top padding
1451 */
1452 public void updateTopPadding(float qsHeight, int scrollY, boolean animate,
1453 boolean ignoreIntrinsicPadding) {
Selim Cinek33d46142016-01-15 18:58:39 -08001454 float start = qsHeight - scrollY;
Selim Cinek1408eb52014-06-02 14:45:38 +02001455 float stackHeight = getHeight() - start;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001456 int minStackHeight = getMinStackHeight();
1457 if (stackHeight <= minStackHeight) {
1458 float overflow = minStackHeight - stackHeight;
1459 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001460 start = getHeight() - stackHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001461 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02001462 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001463 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001464 }
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001465 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1466 animate);
1467 setStackHeight(mLastSetStackHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02001468 }
1469
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001470 public int getMinStackHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001471 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek31aada42015-12-18 17:51:15 -08001472 final int firstChildMinHeight = firstChild != null ? firstChild.getMinHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08001473 : mCollapsedSize;
1474 return firstChildMinHeight + mBottomStackPeekSize + mCollapseSecondCardPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001475 }
1476
1477 public float getTopPaddingOverflow() {
1478 return mTopPaddingOverflow;
1479 }
1480
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001481 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001482 final ExpandableView firstChild = getFirstChildNotGone();
1483 final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
1484 : mCollapsedSize;
1485 return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001486 + mCollapseSecondCardPadding;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001487 }
1488
Selim Cinek1408eb52014-06-02 14:45:38 +02001489 private int clampPadding(int desiredPadding) {
1490 return Math.max(desiredPadding, mIntrinsicPadding);
1491 }
1492
Selim Cinekfed1ab62014-06-17 14:10:33 -07001493 private float getRubberBandFactor(boolean onTop) {
1494 if (!onTop) {
1495 return RUBBER_BAND_FACTOR_NORMAL;
1496 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001497 if (mExpandedInThisMotion) {
1498 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001499 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001500 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1501 } else if (mScrolledToTopOnFirstDown) {
1502 return 1.0f;
1503 }
1504 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02001505 }
1506
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001507 /**
1508 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1509 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1510 * overscroll view (e.g. expand QS).
1511 */
1512 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001513 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001514 || !mScrolledToTopOnFirstDown;
1515 }
1516
Selim Cinek67b22602014-03-10 15:40:16 +01001517 private void endDrag() {
1518 setIsBeingDragged(false);
1519
1520 recycleVelocityTracker();
1521
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001522 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1523 setOverScrollAmount(0, true /* onTop */, true /* animate */);
1524 }
1525 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1526 setOverScrollAmount(0, false /* onTop */, true /* animate */);
1527 }
Selim Cinek67b22602014-03-10 15:40:16 +01001528 }
1529
Jorim Jaggi56306252014-07-03 00:40:09 +02001530 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1531 ev.offsetLocation(sourceView.getX(), sourceView.getY());
1532 ev.offsetLocation(-targetView.getX(), -targetView.getY());
1533 }
1534
Selim Cinek67b22602014-03-10 15:40:16 +01001535 @Override
1536 public boolean onInterceptTouchEvent(MotionEvent ev) {
Jorim Jaggi56306252014-07-03 00:40:09 +02001537 if (mInterceptDelegateEnabled) {
1538 transformTouchEvent(ev, this, mScrollView);
1539 if (mScrollView.onInterceptTouchEvent(ev)) {
1540 mDelegateToScrollView = true;
1541 removeLongPressCallback();
1542 return true;
1543 }
1544 transformTouchEvent(ev, mScrollView, this);
1545 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001546 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001547 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001548 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001549 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001550 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1551 }
Selim Cinek67b22602014-03-10 15:40:16 +01001552 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001553 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01001554 scrollWantsIt = onInterceptTouchEventScroll(ev);
1555 }
1556 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001557 if (!mIsBeingDragged
1558 && !mExpandingNotification
1559 && !mExpandedInThisMotion
1560 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001561 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1562 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001563 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1564 }
1565
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001566 private void handleEmptySpaceClick(MotionEvent ev) {
1567 switch (ev.getActionMasked()) {
1568 case MotionEvent.ACTION_MOVE:
1569 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1570 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1571 mTouchIsClick = false;
1572 }
1573 break;
1574 case MotionEvent.ACTION_UP:
1575 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1576 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1577 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1578 }
1579 break;
1580 }
1581 }
1582
Selim Cinek1408eb52014-06-02 14:45:38 +02001583 private void initDownStates(MotionEvent ev) {
1584 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1585 mExpandedInThisMotion = false;
1586 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001587 mDisallowScrollingInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001588 mTouchIsClick = true;
1589 mInitialTouchX = ev.getX();
1590 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001591 }
Selim Cinek67b22602014-03-10 15:40:16 +01001592 }
1593
Selim Cinekef5127e2015-12-21 16:55:58 -08001594 public void setChildTransferInProgress(boolean childTransferInProgress) {
1595 mChildTransferInProgress = childTransferInProgress;
1596 }
1597
Christoph Studer068f5922014-04-08 17:43:07 -04001598 @Override
Adam Powell6690d012015-06-17 16:41:56 -07001599 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04001600 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01001601 // we only call our internal methods if this is actually a removal and not just a
1602 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08001603 if (!mChildTransferInProgress) {
Selim Cinekb5605e52015-02-20 18:21:41 +01001604 onViewRemovedInternal(child);
1605 }
1606 }
1607
1608 private void onViewRemovedInternal(View child) {
Selim Cinekb55386d2015-12-16 17:26:49 -08001609 mStackScrollAlgorithm.notifyChildrenChanged(this);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001610 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001611 // This is only a position change, don't do anything special
1612 return;
1613 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02001614 ((ExpandableView) child).setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04001615 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02001616 updateScrollStateForRemovedChild(child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07001617 boolean animationGenerated = generateRemoveAnimation(child);
1618 if (animationGenerated && !mSwipedOutViews.contains(child)) {
1619 // Add this view to an overlay in order to ensure that it will still be temporary
1620 // drawn when removed
1621 getOverlay().add(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001622 }
Selim Cinekcab4a602014-09-03 14:47:57 +02001623 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02001624
1625 // Make sure the clipRect we might have set is removed
Selim Cineka272dfe2015-02-20 18:12:28 +01001626 ((ExpandableView) child).setClipTopOptimization(0);
Selim Cinekc27437b2014-05-14 10:23:33 +02001627 }
1628
Selim Cinekb5605e52015-02-20 18:21:41 +01001629 private boolean isChildInGroup(View child) {
1630 return child instanceof ExpandableNotificationRow
1631 && mGroupManager.isChildInGroupWithSummary(
1632 ((ExpandableNotificationRow) child).getStatusBarNotification());
1633 }
1634
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001635 /**
1636 * Generate a remove animation for a child view.
1637 *
1638 * @param child The view to generate the remove animation for.
1639 * @return Whether an animation was generated.
1640 */
1641 private boolean generateRemoveAnimation(View child) {
Selim Cineke0890e52015-06-17 11:17:08 -07001642 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07001643 mAddedHeadsUpChildren.remove(child);
1644 return false;
1645 }
Selim Cinek0fccc722015-07-29 17:04:36 -07001646 if (isClickedHeadsUp(child)) {
1647 // An animation is already running, add it to the Overlay
1648 mClearOverlayViewsWhenFinished.add(child);
1649 return true;
1650 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001651 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07001652 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02001653 // Generate Animations
1654 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001655 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001656 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02001657 } else {
1658 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02001659 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001660 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02001661 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001662 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001663 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001664 }
1665
Selim Cinek0fccc722015-07-29 17:04:36 -07001666 private boolean isClickedHeadsUp(View child) {
1667 return HeadsUpManager.isClickedHeadsUpNotification(child);
1668 }
1669
Selim Cineke0890e52015-06-17 11:17:08 -07001670 /**
1671 * Remove a removed child view from the heads up animations if it was just added there
1672 *
1673 * @return whether any child was removed from the list to animate
1674 */
1675 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
1676 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07001677 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
1678 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07001679 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07001680 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07001681 mTmpList.add(eventPair);
1682 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07001683 }
1684 }
Selim Cineke0890e52015-06-17 11:17:08 -07001685 if (hasAddEvent) {
1686 // This child was just added lets remove all events.
1687 mHeadsUpChangeAnimations.removeAll(mTmpList);
1688 }
1689 mTmpList.clear();
1690 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07001691 }
1692
Selim Cinek572bbd42014-04-25 16:43:27 +02001693 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01001694 * @param child the child to query
1695 * @return whether a view is not a top level child but a child notification and that group is
1696 * not expanded
1697 */
1698 private boolean isChildInInvisibleGroup(View child) {
1699 if (child instanceof ExpandableNotificationRow) {
1700 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1701 ExpandableNotificationRow groupSummary =
1702 mGroupManager.getGroupSummary(row.getStatusBarNotification());
1703 if (groupSummary != null && groupSummary != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07001704 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01001705 }
1706 }
1707 return false;
1708 }
1709
1710 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02001711 * Updates the scroll position when a child was removed
1712 *
1713 * @param removedChild the removed child
1714 */
1715 private void updateScrollStateForRemovedChild(View removedChild) {
1716 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinekd7c4e002014-07-04 18:36:42 +02001717 int childHeight = getIntrinsicHeight(removedChild) + mPaddingBetweenElements;
Selim Cinek572bbd42014-04-25 16:43:27 +02001718 int endPosition = startingPosition + childHeight;
1719 if (endPosition <= mOwnScrollY) {
1720 // This child is fully scrolled of the top, so we have to deduct its height from the
1721 // scrollPosition
1722 mOwnScrollY -= childHeight;
1723 } else if (startingPosition < mOwnScrollY) {
1724 // This child is currently being scrolled into, set the scroll position to the start of
1725 // this child
1726 mOwnScrollY = startingPosition;
1727 }
1728 }
1729
Selim Cinekd7c4e002014-07-04 18:36:42 +02001730 private int getIntrinsicHeight(View view) {
1731 if (view instanceof ExpandableView) {
1732 ExpandableView expandableView = (ExpandableView) view;
1733 return expandableView.getIntrinsicHeight();
1734 }
1735 return view.getHeight();
1736 }
1737
Selim Cinek572bbd42014-04-25 16:43:27 +02001738 private int getPositionInLinearLayout(View requestedChild) {
1739 int position = 0;
1740 for (int i = 0; i < getChildCount(); i++) {
1741 View child = getChildAt(i);
1742 if (child == requestedChild) {
1743 return position;
1744 }
1745 if (child.getVisibility() != View.GONE) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02001746 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02001747 if (i < getChildCount()-1) {
1748 position += mPaddingBetweenElements;
1749 }
1750 }
1751 }
1752 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02001753 }
1754
1755 @Override
Adam Powell6690d012015-06-17 16:41:56 -07001756 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02001757 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01001758 onViewAddedInternal(child);
1759 }
1760
1761 private void onViewAddedInternal(View child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07001762 updateHideSensitiveForChild(child);
Selim Cinekb55386d2015-12-16 17:26:49 -08001763 mStackScrollAlgorithm.notifyChildrenChanged(this);
Jorim Jaggibe565df2014-04-28 17:51:23 +02001764 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00001765 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02001766 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02001767 updateChronometerForChild(child);
Selim Cinek7d5f3742014-11-07 18:07:49 +01001768 if (canChildBeDismissed(child)) {
1769 // Make sure the dismissButton is visible and not in the animated state.
1770 // We need to do this to avoid a race where a clearable notification is added after the
1771 // dismiss animation is finished
1772 mDismissView.showClearButton();
1773 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001774 }
1775
Selim Cinekd06c41c2015-07-06 14:51:36 -07001776 private void updateHideSensitiveForChild(View child) {
1777 if (mAmbientState.isHideSensitive() && child instanceof ExpandableView) {
1778 ExpandableView expandableView = (ExpandableView) child;
1779 expandableView.setHideSensitiveForIntrinsicHeight(true);
1780 }
1781 }
1782
Selim Cinekb5605e52015-02-20 18:21:41 +01001783 public void notifyGroupChildRemoved(View row) {
1784 onViewRemovedInternal(row);
1785 }
1786
1787 public void notifyGroupChildAdded(View row) {
1788 onViewAddedInternal(row);
1789 }
1790
Jorim Jaggi75c95042014-05-16 19:09:59 +02001791 public void setAnimationsEnabled(boolean animationsEnabled) {
1792 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02001793 updateNotificationAnimationStates();
1794 }
1795
1796 private void updateNotificationAnimationStates() {
Selim Cinek8d490d42015-04-10 00:05:50 -07001797 boolean running = mAnimationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02001798 int childCount = getChildCount();
1799 for (int i = 0; i < childCount; i++) {
1800 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07001801 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02001802 updateAnimationState(running, child);
1803 }
1804 }
1805
Selim Cinek51ae05d2014-09-09 15:51:38 +02001806 private void updateAnimationState(View child) {
Selim Cinek8d490d42015-04-10 00:05:50 -07001807 updateAnimationState((mAnimationsEnabled || isPinnedHeadsUp(child)) && mIsExpanded, child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02001808 }
1809
1810
Selim Cinekcab4a602014-09-03 14:47:57 +02001811 private void updateAnimationState(boolean running, View child) {
1812 if (child instanceof ExpandableNotificationRow) {
1813 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1814 row.setIconAnimationRunning(running);
1815 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02001816 }
1817
1818 public boolean isAddOrRemoveAnimationPending() {
1819 return mNeedsAnimation
1820 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
1821 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001822 /**
1823 * Generate an animation for an added child view.
1824 *
1825 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00001826 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001827 */
Jorim Jaggif6411742014-08-05 17:10:43 +00001828 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001829 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001830 // Generate Animations
1831 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00001832 if (fromMoreCard) {
1833 mFromMoreCardAdditions.add(child);
1834 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001835 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02001836 }
Adrian Roos777ef562015-12-01 17:37:14 -08001837 if (isHeadsUp(child) && !mChangePositionInProgress) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001838 mAddedHeadsUpChildren.add(child);
1839 mChildrenToAddAnimated.remove(child);
1840 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001841 }
1842
1843 /**
1844 * Change the position of child to a new location
1845 *
1846 * @param child the view to change the position for
1847 * @param newIndex the new index
1848 */
1849 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04001850 int currentIndex = indexOfChild(child);
1851 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001852 mChangePositionInProgress = true;
Selim Cinekc27437b2014-05-14 10:23:33 +02001853 removeView(child);
1854 addView(child, newIndex);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001855 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04001856 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02001857 mChildrenChangingPositions.add(child);
1858 mNeedsAnimation = true;
1859 }
Selim Cinek572bbd42014-04-25 16:43:27 +02001860 }
1861 }
1862
Selim Cinekf4c19962014-05-01 21:55:31 +02001863 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001864 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001865 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001866 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001867 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001868 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001869 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
1870 mGoToFullShadeDelay);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001871 mAnimationEvents.clear();
Selim Cinekf4c19962014-05-01 21:55:31 +02001872 } else {
1873 applyCurrentState();
1874 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02001875 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02001876 }
1877
1878 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07001879 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02001880 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001881 generateChildAdditionEvents();
1882 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02001883 generateSnapBackEvents();
1884 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001885 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02001886 generateActivateEvent();
1887 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02001888 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04001889 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02001890 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02001891 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01001892 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02001893 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001894 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02001895 }
1896
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001897 private void generateHeadsUpAnimationEvents() {
1898 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001899 ExpandableNotificationRow row = eventPair.first;
1900 boolean isHeadsUp = eventPair.second;
1901 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
1902 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07001903 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07001904 if (!mIsExpanded && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07001905 type = row.wasJustClicked()
1906 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
1907 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinekeaee9c02015-06-25 11:04:20 -04001908 } else {
1909 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
1910 if (viewState == null) {
1911 // A view state was never generated for this view, so we don't need to animate
1912 // this. This may happen with notification children.
1913 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07001914 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04001915 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
1916 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
1917 // Our custom add animation
1918 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
1919 } else {
1920 // Normal add animation
1921 type = AnimationEvent.ANIMATION_TYPE_ADD;
1922 }
1923 onBottom = !pinnedAndClosed;
1924 }
Selim Cineka59ecc32015-04-07 10:51:49 -07001925 }
1926 AnimationEvent event = new AnimationEvent(row, type);
1927 event.headsUpFromBottom = onBottom;
1928 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001929 }
1930 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07001931 mAddedHeadsUpChildren.clear();
1932 }
1933
Selim Cinekeaee9c02015-06-25 11:04:20 -04001934 private boolean shouldHunAppearFromBottom(StackViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07001935 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
1936 return false;
1937 }
1938 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07001939 }
1940
Selim Cinekb5605e52015-02-20 18:21:41 +01001941 private void generateGroupExpansionEvent() {
1942 // Generate a group expansion/collapsing event if there is such a group at all
1943 if (mExpandedGroupView != null) {
1944 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
1945 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
1946 mExpandedGroupView = null;
1947 }
1948 }
1949
Selim Cineka5e211b2014-08-11 17:35:48 +02001950 private void generateViewResizeEvent() {
1951 if (mNeedViewResizeAnimation) {
1952 mAnimationEvents.add(
1953 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
1954 }
1955 mNeedViewResizeAnimation = false;
1956 }
1957
Selim Cinekeb973562014-05-02 17:07:49 +02001958 private void generateSnapBackEvents() {
1959 for (View child : mSnappedBackChildren) {
1960 mAnimationEvents.add(new AnimationEvent(child,
1961 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
1962 }
1963 mSnappedBackChildren.clear();
1964 }
1965
1966 private void generateDragEvents() {
1967 for (View child : mDragAnimPendingChildren) {
1968 mAnimationEvents.add(new AnimationEvent(child,
1969 AnimationEvent.ANIMATION_TYPE_START_DRAG));
1970 }
1971 mDragAnimPendingChildren.clear();
1972 }
1973
Selim Cinek572bbd42014-04-25 16:43:27 +02001974 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02001975 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02001976 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
1977 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02001978 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
1979 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001980 AnimationEvent event = new AnimationEvent(child, animationType);
1981
1982 // we need to know the view after this one
1983 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
1984 mAnimationEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +02001985 }
1986 mSwipedOutViews.clear();
1987 mChildrenToRemoveAnimated.clear();
1988 }
1989
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001990 private void generatePositionChangeEvents() {
1991 for (View child : mChildrenChangingPositions) {
1992 mAnimationEvents.add(new AnimationEvent(child,
1993 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
1994 }
1995 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01001996 if (mGenerateChildOrderChangedEvent) {
1997 mAnimationEvents.add(new AnimationEvent(null,
1998 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
1999 mGenerateChildOrderChangedEvent = false;
2000 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002001 }
2002
Selim Cinek572bbd42014-04-25 16:43:27 +02002003 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002004 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002005 if (mFromMoreCardAdditions.contains(child)) {
2006 mAnimationEvents.add(new AnimationEvent(child,
2007 AnimationEvent.ANIMATION_TYPE_ADD,
2008 StackStateAnimator.ANIMATION_DURATION_STANDARD));
2009 } else {
2010 mAnimationEvents.add(new AnimationEvent(child,
2011 AnimationEvent.ANIMATION_TYPE_ADD));
2012 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002013 }
2014 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002015 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04002016 }
2017
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002018 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02002019 if (mTopPaddingNeedsAnimation) {
2020 mAnimationEvents.add(
2021 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2022 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002023 mTopPaddingNeedsAnimation = false;
2024 }
2025
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002026 private void generateActivateEvent() {
2027 if (mActivateNeedsAnimation) {
2028 mAnimationEvents.add(
2029 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2030 }
2031 mActivateNeedsAnimation = false;
2032 }
2033
Selim Cinekd9acca52014-09-01 22:33:25 +02002034 private void generateAnimateEverythingEvent() {
2035 if (mEverythingNeedsAnimation) {
2036 mAnimationEvents.add(
2037 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2038 }
2039 mEverythingNeedsAnimation = false;
2040 }
2041
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002042 private void generateDimmedEvent() {
2043 if (mDimmedNeedsAnimation) {
2044 mAnimationEvents.add(
2045 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2046 }
2047 mDimmedNeedsAnimation = false;
2048 }
2049
Jorim Jaggiae441282014-08-01 02:45:18 +02002050 private void generateHideSensitiveEvent() {
2051 if (mHideSensitiveNeedsAnimation) {
2052 mAnimationEvents.add(
2053 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2054 }
2055 mHideSensitiveNeedsAnimation = false;
2056 }
2057
John Spurlockbf370992014-06-17 13:58:31 -04002058 private void generateDarkEvent() {
2059 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002060 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2061 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2062 mAnimationEvents.add(ev);
John Spurlockbf370992014-06-17 13:58:31 -04002063 }
2064 mDarkNeedsAnimation = false;
2065 }
2066
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002067 private void generateGoToFullShadeEvent() {
2068 if (mGoToFullShadeNeedsAnimation) {
2069 mAnimationEvents.add(
2070 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2071 }
2072 mGoToFullShadeNeedsAnimation = false;
2073 }
2074
Selim Cinek67b22602014-03-10 15:40:16 +01002075 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002076 if (!isScrollingEnabled()) {
2077 return false;
2078 }
Selim Cinek67b22602014-03-10 15:40:16 +01002079 /*
2080 * This method JUST determines whether we want to intercept the motion.
2081 * If we return true, onMotionEvent will be called and we do the actual
2082 * scrolling there.
2083 */
2084
2085 /*
2086 * Shortcut the most recurring case: the user is in the dragging
Chris Wren5d53df42015-06-26 11:26:03 -04002087 * state and is moving their finger. We want to intercept this
Selim Cinek67b22602014-03-10 15:40:16 +01002088 * motion.
2089 */
2090 final int action = ev.getAction();
2091 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2092 return true;
2093 }
2094
Selim Cinek67b22602014-03-10 15:40:16 +01002095 switch (action & MotionEvent.ACTION_MASK) {
2096 case MotionEvent.ACTION_MOVE: {
2097 /*
2098 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04002099 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01002100 */
2101
2102 /*
2103 * Locally do absolute value. mLastMotionY is set to the y value
2104 * of the down event.
2105 */
2106 final int activePointerId = mActivePointerId;
2107 if (activePointerId == INVALID_POINTER) {
2108 // If we don't have a valid id, the touch down wasn't on content.
2109 break;
2110 }
2111
2112 final int pointerIndex = ev.findPointerIndex(activePointerId);
2113 if (pointerIndex == -1) {
2114 Log.e(TAG, "Invalid pointerId=" + activePointerId
2115 + " in onInterceptTouchEvent");
2116 break;
2117 }
2118
2119 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02002120 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01002121 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02002122 final int xDiff = Math.abs(x - mDownX);
2123 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01002124 setIsBeingDragged(true);
2125 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002126 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01002127 initVelocityTrackerIfNotExists();
2128 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01002129 }
2130 break;
2131 }
2132
2133 case MotionEvent.ACTION_DOWN: {
2134 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05302135 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01002136 if (getChildAtPosition(ev.getX(), y) == null) {
2137 setIsBeingDragged(false);
2138 recycleVelocityTracker();
2139 break;
2140 }
2141
2142 /*
2143 * Remember location of down touch.
2144 * ACTION_DOWN always refers to pointer index 0.
2145 */
2146 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002147 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01002148 mActivePointerId = ev.getPointerId(0);
2149
2150 initOrResetVelocityTracker();
2151 mVelocityTracker.addMovement(ev);
2152 /*
2153 * If being flinged and user touches the screen, initiate drag;
2154 * otherwise don't. mScroller.isFinished should be false when
2155 * being flinged.
2156 */
2157 boolean isBeingDragged = !mScroller.isFinished();
2158 setIsBeingDragged(isBeingDragged);
2159 break;
2160 }
2161
2162 case MotionEvent.ACTION_CANCEL:
2163 case MotionEvent.ACTION_UP:
2164 /* Release the drag */
2165 setIsBeingDragged(false);
2166 mActivePointerId = INVALID_POINTER;
2167 recycleVelocityTracker();
2168 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2169 postInvalidateOnAnimation();
2170 }
2171 break;
2172 case MotionEvent.ACTION_POINTER_UP:
2173 onSecondaryPointerUp(ev);
2174 break;
2175 }
2176
2177 /*
2178 * The only time we want to intercept motion events is if we are in the
2179 * drag mode.
2180 */
2181 return mIsBeingDragged;
2182 }
2183
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002184 /**
2185 * @return Whether the specified motion event is actually happening over the content.
2186 */
2187 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01002188 return isInContentBounds(event.getY());
2189 }
2190
2191 /**
2192 * @return Whether a y coordinate is inside the content.
2193 */
2194 public boolean isInContentBounds(float y) {
2195 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002196 }
2197
Selim Cinek67b22602014-03-10 15:40:16 +01002198 private void setIsBeingDragged(boolean isDragged) {
2199 mIsBeingDragged = isDragged;
2200 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002201 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02002202 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002203 }
2204 }
2205
2206 @Override
2207 public void onWindowFocusChanged(boolean hasWindowFocus) {
2208 super.onWindowFocusChanged(hasWindowFocus);
2209 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002210 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002211 }
2212 }
Selim Cinekfab078b2014-03-27 22:45:58 +01002213
Selim Cinek1408eb52014-06-02 14:45:38 +02002214 public void removeLongPressCallback() {
2215 mSwipeHelper.removeLongPressCallback();
2216 }
2217
Selim Cinekfab078b2014-03-27 22:45:58 +01002218 @Override
2219 public boolean isScrolledToTop() {
2220 return mOwnScrollY == 0;
2221 }
2222
2223 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002224 public boolean isScrolledToBottom() {
2225 return mOwnScrollY >= getScrollRange();
2226 }
2227
2228 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01002229 public View getHostView() {
2230 return this;
2231 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002232
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002233 public int getEmptyBottomMargin() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002234 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002235 if (needsHeightAdaption()) {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002236 emptyMargin -= mBottomStackSlowDownHeight;
Jorim Jaggi1d480692014-05-20 19:41:58 +02002237 } else {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002238 emptyMargin -= mCollapseSecondCardPadding;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002239 }
2240 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002241 }
2242
Selim Cinek5f71bee2015-11-18 10:25:23 -08002243 public float getKeyguardBottomStackSize() {
2244 return mBottomStackPeekSize + getResources().getDimensionPixelSize(
2245 R.dimen.bottom_stack_slow_down_length);
2246 }
2247
Selim Cinek1685e632014-04-08 02:27:49 +02002248 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002249 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02002250 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
2251 }
2252
2253 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002254 mIsExpansionChanging = false;
Selim Cinek1685e632014-04-08 02:27:49 +02002255 mStackScrollAlgorithm.onExpansionStopped();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002256 if (!mIsExpanded) {
2257 mOwnScrollY = 0;
Selim Cinekf336f4c2014-11-12 16:58:16 +01002258
2259 // lets make sure nothing is in the overlay anymore
2260 getOverlay().clear();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002261 }
Selim Cinek1685e632014-04-08 02:27:49 +02002262 }
2263
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002264 public void onPanelTrackingStarted() {
2265 mPanelTracking = true;
2266 }
2267 public void onPanelTrackingStopped() {
2268 mPanelTracking = false;
2269 }
2270
Selim Cinekb24e0a92015-06-09 20:17:30 -07002271 public void resetScrollPosition() {
2272 mScroller.abortAnimation();
2273 mOwnScrollY = 0;
2274 }
2275
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02002276 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02002277 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02002278 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02002279 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002280 if (changed) {
2281 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02002282 updateChronometers();
2283 }
2284 }
2285
2286 private void updateChronometers() {
2287 int childCount = getChildCount();
2288 for (int i = 0; i < childCount; i++) {
2289 updateChronometerForChild(getChildAt(i));
2290 }
2291 }
2292
2293 private void updateChronometerForChild(View child) {
2294 if (child instanceof ExpandableNotificationRow) {
2295 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2296 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002297 }
Selim Cinek1685e632014-04-08 02:27:49 +02002298 }
2299
Jorim Jaggibe565df2014-04-28 17:51:23 +02002300 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01002301 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002302 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002303 updateScrollPositionOnExpandInBottom(view);
2304 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02002305 notifyHeightChangeListener(view);
Selim Cinekb5605e52015-02-20 18:21:41 +01002306 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08002307 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
2308 ? (ExpandableNotificationRow) view
2309 : null;
2310 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01002311 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002312 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02002313 }
2314
Selim Cineka5e211b2014-08-11 17:35:48 +02002315 @Override
2316 public void onReset(ExpandableView view) {
Selim Cinek0e41dea2014-08-25 13:55:06 +02002317 if (mIsExpanded && mAnimationsEnabled) {
2318 mRequestViewResizeAnimationOnLayout = true;
2319 }
Selim Cinek31094df2014-08-14 19:28:15 +02002320 mStackScrollAlgorithm.onReset(view);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002321 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02002322 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02002323 }
2324
Selim Cinekf7a14c02014-07-07 14:01:46 +02002325 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2326 if (view instanceof ExpandableNotificationRow) {
2327 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002328 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinekf7a14c02014-07-07 14:01:46 +02002329 // We are actually expanding this view
Selim Cinek388df6d2015-10-22 13:25:11 -07002330 float endPosition;
2331 if (row.isChildInGroup()) {
2332 ExpandableNotificationRow parent = row.getNotificationParent();
2333 endPosition = parent.getTranslationY() + parent.getActualHeight();
2334 } else {
2335 endPosition = row.getTranslationY() + row.getActualHeight();
2336 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02002337 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
Selim Cinekcb9400a2015-06-03 16:56:13 +02002338 mBottomStackSlowDownHeight + (int) mStackTranslation;
Selim Cinekf7a14c02014-07-07 14:01:46 +02002339 if (endPosition > stackEnd) {
2340 mOwnScrollY += endPosition - stackEnd;
2341 mDisallowScrollingInThisMotion = true;
2342 }
2343 }
2344 }
2345 }
2346
Jorim Jaggibe565df2014-04-28 17:51:23 +02002347 public void setOnHeightChangedListener(
2348 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2349 this.mOnHeightChangedListener = mOnHeightChangedListener;
2350 }
2351
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002352 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2353 mOnEmptySpaceClickListener = listener;
2354 }
2355
Selim Cinek572bbd42014-04-25 16:43:27 +02002356 public void onChildAnimationFinished() {
Selim Cinek319bdc42014-05-01 23:01:58 +02002357 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07002358 runAnimationFinishedRunnables();
Selim Cinek0fccc722015-07-29 17:04:36 -07002359 clearViewOverlays();
2360 }
2361
2362 private void clearViewOverlays() {
2363 for (View view : mClearOverlayViewsWhenFinished) {
2364 getOverlay().remove(view);
2365 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002366 }
2367
2368 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002369 for (Runnable runnable : mAnimationFinishedRunnables) {
2370 runnable.run();
2371 }
2372 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002373 }
2374
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002375 /**
2376 * See {@link AmbientState#setDimmed}.
2377 */
2378 public void setDimmed(boolean dimmed, boolean animate) {
2379 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002380 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002381 mDimmedNeedsAnimation = true;
2382 mNeedsAnimation = true;
2383 }
2384 requestChildrenUpdate();
2385 }
2386
Jorim Jaggiae441282014-08-01 02:45:18 +02002387 public void setHideSensitive(boolean hideSensitive, boolean animate) {
2388 if (hideSensitive != mAmbientState.isHideSensitive()) {
2389 int childCount = getChildCount();
2390 for (int i = 0; i < childCount; i++) {
2391 ExpandableView v = (ExpandableView) getChildAt(i);
2392 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2393 }
2394 mAmbientState.setHideSensitive(hideSensitive);
2395 if (animate && mAnimationsEnabled) {
2396 mHideSensitiveNeedsAnimation = true;
2397 mNeedsAnimation = true;
2398 }
2399 requestChildrenUpdate();
2400 }
2401 }
2402
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002403 /**
2404 * See {@link AmbientState#setActivatedChild}.
2405 */
Selim Cineka32ab602014-06-11 15:06:01 +02002406 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002407 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002408 if (mAnimationsEnabled) {
2409 mActivateNeedsAnimation = true;
2410 mNeedsAnimation = true;
2411 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002412 requestChildrenUpdate();
2413 }
2414
Selim Cineka32ab602014-06-11 15:06:01 +02002415 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002416 return mAmbientState.getActivatedChild();
2417 }
2418
Selim Cinek572bbd42014-04-25 16:43:27 +02002419 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02002420 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02002421 if (mListener != null) {
2422 mListener.onChildLocationsChanged(this);
2423 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002424 runAnimationFinishedRunnables();
Selim Cinek572bbd42014-04-25 16:43:27 +02002425 }
2426
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002427 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002428 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002429 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002430 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002431 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002432 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002433 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02002434 }
2435
Selim Cinek1408eb52014-06-02 14:45:38 +02002436 public void cancelExpandHelper() {
2437 mExpandHelper.cancel();
2438 }
2439
2440 public void setIntrinsicPadding(int intrinsicPadding) {
2441 mIntrinsicPadding = intrinsicPadding;
2442 }
2443
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002444 public int getIntrinsicPadding() {
2445 return mIntrinsicPadding;
2446 }
2447
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002448 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02002449 * @return the y position of the first notification
2450 */
2451 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07002452 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02002453 }
2454
Selim Cinekc0ce82d2014-06-10 13:21:15 +02002455 @Override
2456 public boolean shouldDelayChildPressedState() {
2457 return true;
2458 }
2459
Jorim Jaggi457cc352014-06-02 22:47:42 +02002460 /**
John Spurlockbf370992014-06-17 13:58:31 -04002461 * See {@link AmbientState#setDark}.
2462 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002463 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04002464 mAmbientState.setDark(dark);
2465 if (animate && mAnimationsEnabled) {
2466 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002467 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04002468 mNeedsAnimation = true;
2469 }
2470 requestChildrenUpdate();
2471 }
2472
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002473 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
2474 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
2475 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2476 }
2477 if (screenLocation.y > getBottomMostNotificationBottom()) {
2478 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
2479 }
2480 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
2481 if (child != null) {
2482 return getNotGoneIndex(child);
2483 } else {
2484 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2485 }
2486 }
2487
2488 private int getNotGoneIndex(View child) {
2489 int count = getChildCount();
2490 int notGoneIndex = 0;
2491 for (int i = 0; i < count; i++) {
2492 View v = getChildAt(i);
2493 if (child == v) {
2494 return notGoneIndex;
2495 }
2496 if (v.getVisibility() != View.GONE) {
2497 notGoneIndex++;
2498 }
2499 }
2500 return -1;
2501 }
2502
Dan Sandlereceda3d2014-07-21 15:35:01 -04002503 public void setDismissView(DismissView dismissView) {
2504 mDismissView = dismissView;
2505 addView(mDismissView);
2506 }
2507
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002508 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
2509 mEmptyShadeView = emptyShadeView;
2510 addView(mEmptyShadeView);
2511 }
2512
2513 public void updateEmptyShadeView(boolean visible) {
2514 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
2515 int newVisibility = visible ? VISIBLE : GONE;
2516 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02002517 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002518 if (mEmptyShadeView.willBeGone()) {
2519 mEmptyShadeView.cancelAnimation();
2520 } else {
2521 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002522 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02002523 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002524 mEmptyShadeView.setWillBeGone(false);
2525 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07002526 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002527 } else {
Selim Cinek20867102014-12-10 17:09:17 +01002528 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002529 @Override
2530 public void run() {
2531 mEmptyShadeView.setVisibility(GONE);
2532 mEmptyShadeView.setWillBeGone(false);
2533 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07002534 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002535 }
Selim Cinek20867102014-12-10 17:09:17 +01002536 };
2537 if (mAnimationsEnabled) {
2538 mEmptyShadeView.setWillBeGone(true);
2539 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
2540 } else {
2541 mEmptyShadeView.setInvisible();
2542 onFinishedRunnable.run();
2543 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002544 }
2545 }
2546 }
2547
Selim Cinek2cd45df2015-06-09 18:00:07 -07002548 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
2549 mOverflowContainer = overFlowContainer;
2550 addView(mOverflowContainer);
2551 }
2552
2553 public void updateOverflowContainerVisibility(boolean visible) {
2554 int oldVisibility = mOverflowContainer.willBeGone() ? GONE
2555 : mOverflowContainer.getVisibility();
2556 final int newVisibility = visible ? VISIBLE : GONE;
2557 if (oldVisibility != newVisibility) {
2558 Runnable onFinishedRunnable = new Runnable() {
2559 @Override
2560 public void run() {
2561 mOverflowContainer.setVisibility(newVisibility);
2562 mOverflowContainer.setWillBeGone(false);
2563 updateContentHeight();
2564 notifyHeightChangeListener(mOverflowContainer);
2565 }
2566 };
2567 if (!mAnimationsEnabled || !mIsExpanded) {
2568 mOverflowContainer.cancelAppearDrawing();
2569 onFinishedRunnable.run();
2570 } else if (newVisibility != GONE) {
2571 mOverflowContainer.performAddAnimation(0,
2572 StackStateAnimator.ANIMATION_DURATION_STANDARD);
2573 mOverflowContainer.setVisibility(newVisibility);
2574 mOverflowContainer.setWillBeGone(false);
2575 updateContentHeight();
2576 notifyHeightChangeListener(mOverflowContainer);
2577 } else {
2578 mOverflowContainer.performRemoveAnimation(
2579 StackStateAnimator.ANIMATION_DURATION_STANDARD,
2580 0.0f,
2581 onFinishedRunnable);
2582 mOverflowContainer.setWillBeGone(true);
2583 }
2584 }
2585 }
2586
Dan Sandlereceda3d2014-07-21 15:35:01 -04002587 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002588 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002589 int newVisibility = visible ? VISIBLE : GONE;
2590 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02002591 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002592 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002593 mDismissView.cancelAnimation();
2594 } else {
2595 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04002596 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02002597 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002598 mDismissView.setWillBeGone(false);
2599 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002600 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002601 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01002602 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002603 @Override
2604 public void run() {
2605 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002606 mDismissView.setWillBeGone(false);
2607 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02002608 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002609 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01002610 };
Selim Cinek20867102014-12-10 17:09:17 +01002611 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01002612 mDismissView.setWillBeGone(true);
2613 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
2614 } else {
2615 dimissHideFinishRunnable.run();
2616 mDismissView.showClearButton();
2617 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002618 }
2619 }
2620 }
2621
2622 public void setDismissAllInProgress(boolean dismissAllInProgress) {
2623 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek7d5f3742014-11-07 18:07:49 +01002624 mDismissView.setDismissAllInProgress(dismissAllInProgress);
Selim Cinek9c17b772015-07-07 20:37:09 -07002625 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cineka272dfe2015-02-20 18:12:28 +01002626 if (dismissAllInProgress) {
2627 disableClipOptimization();
2628 }
Selim Cinek9c17b772015-07-07 20:37:09 -07002629 handleDismissAllClipping();
2630 }
2631
2632 private void handleDismissAllClipping() {
2633 final int count = getChildCount();
2634 boolean previousChildWillBeDismissed = false;
2635 for (int i = 0; i < count; i++) {
2636 ExpandableView child = (ExpandableView) getChildAt(i);
2637 if (child.getVisibility() == GONE) {
2638 continue;
2639 }
2640 if (mDismissAllInProgress && previousChildWillBeDismissed) {
2641 child.setMinClipTopAmount(child.getClipTopAmount());
2642 } else {
2643 child.setMinClipTopAmount(0);
2644 }
2645 previousChildWillBeDismissed = canChildBeDismissed(child);
2646 }
Selim Cineka272dfe2015-02-20 18:12:28 +01002647 }
2648
2649 private void disableClipOptimization() {
2650 final int count = getChildCount();
2651 for (int i = 0; i < count; i++) {
2652 ExpandableView child = (ExpandableView) getChildAt(i);
2653 if (child.getVisibility() == GONE) {
2654 continue;
2655 }
2656 child.setClipTopOptimization(0);
2657 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002658 }
2659
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002660 public boolean isDismissViewNotGone() {
2661 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
2662 }
2663
2664 public boolean isDismissViewVisible() {
2665 return mDismissView.isVisible();
2666 }
2667
2668 public int getDismissViewHeight() {
Jorim Jaggi1d49ec92014-08-25 18:44:01 +02002669 int height = mDismissView.getHeight() + mPaddingBetweenElementsNormal;
2670
2671 // Hack: Accommodate for additional distance when we only have one notification and the
2672 // dismiss all button.
2673 if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView
2674 && getFirstChildNotGone() instanceof ActivatableNotificationView) {
2675 height += mCollapseSecondCardPadding;
2676 }
2677 return height;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02002678 }
2679
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01002680 public int getEmptyShadeViewHeight() {
2681 return mEmptyShadeView.getHeight();
2682 }
2683
Jorim Jaggie0640dd2014-08-05 23:12:40 +02002684 public float getBottomMostNotificationBottom() {
2685 final int count = getChildCount();
2686 float max = 0;
2687 for (int childIdx = 0; childIdx < count; childIdx++) {
2688 ExpandableView child = (ExpandableView) getChildAt(childIdx);
2689 if (child.getVisibility() == GONE) {
2690 continue;
2691 }
2692 float bottom = child.getTranslationY() + child.getActualHeight();
2693 if (bottom > max) {
2694 max = bottom;
2695 }
2696 }
Selim Cinekd2281152015-04-10 14:37:46 -07002697 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02002698 }
2699
Selim Cinek19c8c702014-08-25 22:09:19 +02002700 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
2701 this.mPhoneStatusBar = phoneStatusBar;
2702 }
2703
Selim Cinekb5605e52015-02-20 18:21:41 +01002704 public void setGroupManager(NotificationGroupManager groupManager) {
2705 this.mGroupManager = groupManager;
2706 }
2707
Selim Cinekd9acca52014-09-01 22:33:25 +02002708 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01002709 requestAnimateEverything();
2710 }
2711
2712 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02002713 if (mIsExpanded && mAnimationsEnabled) {
2714 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01002715 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02002716 requestChildrenUpdate();
2717 }
2718 }
2719
Selim Cinek04fb2582015-06-02 19:58:09 +02002720 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01002721 int childCount = getChildCount();
2722 for (int i = childCount - 1; i >= 0; i--) {
2723 ExpandableView child = (ExpandableView) getChildAt(i);
2724 if (child.getVisibility() != View.GONE) {
2725 float childTop = child.getY();
2726 if (childTop > touchY) {
2727 // we are above a notification entirely let's abort
2728 return false;
2729 }
2730 boolean belowChild = touchY > childTop + child.getActualHeight();
2731 if (child == mDismissView) {
2732 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
2733 touchY - childTop)) {
2734 // We clicked on the dismiss button
2735 return false;
2736 }
2737 } else if (child == mEmptyShadeView) {
2738 // We arrived at the empty shade view, for which we accept all clicks
2739 return true;
2740 } else if (!belowChild){
2741 // We are on a child
2742 return false;
2743 }
2744 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002745 }
Selim Cinek04fb2582015-06-02 19:58:09 +02002746 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002747 }
2748
Selim Cinekb5605e52015-02-20 18:21:41 +01002749 @Override
2750 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08002751 boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
Selim Cinekb5605e52015-02-20 18:21:41 +01002752 if (animated) {
2753 mExpandedGroupView = changedRow;
2754 mNeedsAnimation = true;
2755 }
2756 changedRow.setChildrenExpanded(expanded, animated);
2757 onHeightChanged(changedRow, false /* needsAnimation */);
2758 }
2759
2760 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01002761 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
Selim Cinekef5127e2015-12-21 16:55:58 -08002762 mPhoneStatusBar.requestNotificationUpdate();
2763 }
2764
2765 @Override
2766 public void onChildIsolationChanged() {
2767 mPhoneStatusBar.requestNotificationUpdate();
Selim Cinekb5605e52015-02-20 18:21:41 +01002768 }
2769
2770 public void generateChildOrderChangedEvent() {
2771 if (mIsExpanded && mAnimationsEnabled) {
2772 mGenerateChildOrderChangedEvent = true;
2773 mNeedsAnimation = true;
2774 requestChildrenUpdate();
2775 }
2776 }
2777
Selim Cinek684a4422015-04-15 16:18:39 -07002778 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002779 mAnimationFinishedRunnables.add(runnable);
2780 }
2781
2782 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
2783 mHeadsUpManager = headsUpManager;
2784 mAmbientState.setHeadsUpManager(headsUpManager);
2785 }
2786
2787 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
2788 if (mAnimationsEnabled) {
2789 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
2790 mNeedsAnimation = true;
2791 requestChildrenUpdate();
2792 }
2793 }
2794
2795 public void setShadeExpanded(boolean shadeExpanded) {
2796 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07002797 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002798 }
2799
Selim Cineka59ecc32015-04-07 10:51:49 -07002800 /**
2801 * Set the boundary for the bottom heads up position. The heads up will always be above this
2802 * position.
2803 *
2804 * @param height the height of the screen
2805 * @param bottomBarHeight the height of the bar on the bottom
2806 */
2807 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
2808 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
2809 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002810 requestChildrenUpdate();
2811 }
2812
2813 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
2814 mTrackingHeadsUp = trackingHeadsUp;
2815 }
2816
Selim Cinekaac93252015-04-14 20:04:12 -07002817 public void setScrimController(ScrimController scrimController) {
2818 mScrimController = scrimController;
2819 }
2820
Selim Cinekbbc580b2015-06-03 14:11:03 +02002821 public void forceNoOverlappingRendering(boolean force) {
2822 mForceNoOverlappingRendering = force;
2823 }
2824
2825 @Override
2826 public boolean hasOverlappingRendering() {
2827 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
2828 }
2829
Selim Cinek3afd00e2014-08-11 22:32:57 +02002830 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002831 * A listener that is notified when some child locations might have changed.
2832 */
2833 public interface OnChildLocationsChangedListener {
2834 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
2835 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002836
Jorim Jaggi290600a2014-05-30 17:02:20 +02002837 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002838 * A listener that is notified when the empty space below the notifications is clicked on
2839 */
2840 public interface OnEmptySpaceClickListener {
2841 public void onEmptySpaceClicked(float x, float y);
2842 }
2843
2844 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02002845 * A listener that gets notified when the overscroll at the top has changed.
2846 */
2847 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02002848
2849 /**
2850 * Notifies a listener that the overscroll has changed.
2851 *
2852 * @param amount the amount of overscroll, in pixels
2853 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
2854 * unrubberbanded motion to directly expand overscroll view (e.g expand
2855 * QS)
2856 */
2857 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02002858
2859 /**
2860 * Notify a listener that the scroller wants to escape from the scrolling motion and
2861 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
2862 *
2863 * @param velocity The velocity that the Scroller had when over flinging
2864 * @param open Should the fling open or close the overscroll view.
2865 */
2866 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02002867 }
2868
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002869 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02002870
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002871 static AnimationFilter[] FILTERS = new AnimationFilter[] {
2872
2873 // ANIMATION_TYPE_ADD
2874 new AnimationFilter()
2875 .animateAlpha()
2876 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002877 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002878 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002879 .animateZ()
2880 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002881
2882 // ANIMATION_TYPE_REMOVE
2883 new AnimationFilter()
2884 .animateAlpha()
2885 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002886 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002887 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002888 .animateZ()
2889 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002890
2891 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
2892 new AnimationFilter()
2893 .animateAlpha()
2894 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002895 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002896 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002897 .animateZ()
2898 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002899
2900 // ANIMATION_TYPE_TOP_PADDING_CHANGED
2901 new AnimationFilter()
2902 .animateAlpha()
2903 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002904 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002905 .animateY()
2906 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002907 .animateZ(),
2908
2909 // ANIMATION_TYPE_START_DRAG
2910 new AnimationFilter()
2911 .animateAlpha(),
2912
2913 // ANIMATION_TYPE_SNAP_BACK
2914 new AnimationFilter()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002915 .animateAlpha()
2916 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002917
2918 // ANIMATION_TYPE_ACTIVATED_CHILD
2919 new AnimationFilter()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002920 .animateAlpha(),
2921
2922 // ANIMATION_TYPE_DIMMED
2923 new AnimationFilter()
Selim Cinek34c0a8d2014-05-12 00:01:43 +02002924 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002925 .animateDimmed(),
2926
2927 // ANIMATION_TYPE_CHANGE_POSITION
2928 new AnimationFilter()
2929 .animateAlpha()
2930 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02002931 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002932 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04002933 .animateZ(),
2934
2935 // ANIMATION_TYPE_DARK
2936 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01002937 .animateDark()
2938 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002939
2940 // ANIMATION_TYPE_GO_TO_FULL_SHADE
2941 new AnimationFilter()
2942 .animateAlpha()
2943 .animateHeight()
2944 .animateTopInset()
2945 .animateY()
2946 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02002947 .animateZ()
2948 .hasDelays(),
2949
2950 // ANIMATION_TYPE_HIDE_SENSITIVE
2951 new AnimationFilter()
2952 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02002953
2954 // ANIMATION_TYPE_VIEW_RESIZE
2955 new AnimationFilter()
2956 .animateAlpha()
2957 .animateHeight()
2958 .animateTopInset()
2959 .animateY()
2960 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02002961
Selim Cinekb5605e52015-02-20 18:21:41 +01002962 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
2963 new AnimationFilter()
2964 .animateAlpha()
2965 .animateHeight()
2966 .animateTopInset()
2967 .animateY()
2968 .animateZ(),
2969
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002970 // ANIMATION_TYPE_HEADS_UP_APPEAR
2971 new AnimationFilter()
2972 .animateAlpha()
2973 .animateHeight()
2974 .animateTopInset()
2975 .animateY()
2976 .animateZ(),
2977
2978 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
2979 new AnimationFilter()
2980 .animateAlpha()
2981 .animateHeight()
2982 .animateTopInset()
2983 .animateY()
2984 .animateZ(),
2985
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07002986 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
2987 new AnimationFilter()
2988 .animateAlpha()
2989 .animateHeight()
2990 .animateTopInset()
2991 .animateY()
2992 .animateZ()
2993 .hasDelays(),
2994
Selim Cineka59ecc32015-04-07 10:51:49 -07002995 // ANIMATION_TYPE_HEADS_UP_OTHER
2996 new AnimationFilter()
2997 .animateAlpha()
2998 .animateHeight()
2999 .animateTopInset()
3000 .animateY()
3001 .animateZ(),
3002
Selim Cinekd9acca52014-09-01 22:33:25 +02003003 // ANIMATION_TYPE_EVERYTHING
3004 new AnimationFilter()
3005 .animateAlpha()
3006 .animateDark()
Selim Cinekd9acca52014-09-01 22:33:25 +02003007 .animateDimmed()
3008 .animateHideSensitive()
3009 .animateHeight()
3010 .animateTopInset()
3011 .animateY()
3012 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003013 };
3014
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003015 static int[] LENGTHS = new int[] {
3016
3017 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003018 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003019
3020 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003021 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003022
3023 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3024 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3025
3026 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3027 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3028
3029 // ANIMATION_TYPE_START_DRAG
3030 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3031
3032 // ANIMATION_TYPE_SNAP_BACK
3033 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3034
3035 // ANIMATION_TYPE_ACTIVATED_CHILD
3036 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
3037
3038 // ANIMATION_TYPE_DIMMED
3039 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003040
3041 // ANIMATION_TYPE_CHANGE_POSITION
3042 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04003043
3044 // ANIMATION_TYPE_DARK
3045 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003046
3047 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3048 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02003049
3050 // ANIMATION_TYPE_HIDE_SENSITIVE
3051 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02003052
3053 // ANIMATION_TYPE_VIEW_RESIZE
3054 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02003055
Selim Cinekb5605e52015-02-20 18:21:41 +01003056 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08003057 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01003058
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003059 // ANIMATION_TYPE_HEADS_UP_APPEAR
3060 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
3061
3062 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3063 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3064
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003065 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3066 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3067
Selim Cineka59ecc32015-04-07 10:51:49 -07003068 // ANIMATION_TYPE_HEADS_UP_OTHER
3069 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3070
Selim Cinekd9acca52014-09-01 22:33:25 +02003071 // ANIMATION_TYPE_EVERYTHING
3072 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003073 };
3074
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003075 static final int ANIMATION_TYPE_ADD = 0;
3076 static final int ANIMATION_TYPE_REMOVE = 1;
3077 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
3078 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
3079 static final int ANIMATION_TYPE_START_DRAG = 4;
3080 static final int ANIMATION_TYPE_SNAP_BACK = 5;
3081 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
3082 static final int ANIMATION_TYPE_DIMMED = 7;
3083 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04003084 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003085 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02003086 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02003087 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01003088 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003089 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
3090 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003091 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
3092 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
3093 static final int ANIMATION_TYPE_EVERYTHING = 18;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003094
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003095 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
3096 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
3097
Selim Cinek572bbd42014-04-25 16:43:27 +02003098 final long eventStartTime;
3099 final View changingView;
3100 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003101 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003102 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003103 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003104 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07003105 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02003106
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003107 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003108 this(view, type, LENGTHS[type]);
3109 }
3110
3111 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003112 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
3113 changingView = view;
3114 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003115 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003116 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003117 }
3118
3119 /**
3120 * Combines the length of several animation events into a single value.
3121 *
3122 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003123 * @return The combined length. Depending on the event types, this might be the maximum of
3124 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003125 */
3126 static long combineLength(ArrayList<AnimationEvent> events) {
3127 long length = 0;
3128 int size = events.size();
3129 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003130 AnimationEvent event = events.get(i);
3131 length = Math.max(length, event.length);
3132 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
3133 return event.length;
3134 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003135 }
3136 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02003137 }
3138 }
3139
Selim Cinek67b22602014-03-10 15:40:16 +01003140}