blob: bd5dcc6736de45be97aaab94539a7202158304f4 [file] [log] [blame]
Selim Cinek67b22602014-03-10 15:40:16 +01001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.systemui.statusbar.stack;
18
Selim Cinek614576e2016-01-20 10:54:09 -080019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.PropertyValuesHolder;
Selim Cinekd35c2792016-01-21 13:20:57 -080023import android.animation.TimeAnimator;
24import android.animation.ValueAnimator;
Mady Mellor4b80b102016-01-22 08:03:58 -080025import android.animation.ValueAnimator.AnimatorUpdateListener;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010026import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010027import android.content.Context;
28import android.content.res.Configuration;
Selim Cinek67b22602014-03-10 15:40:16 +010029import android.graphics.Canvas;
Selim Cinekd35c2792016-01-21 13:20:57 -080030import android.graphics.Color;
Selim Cinek67b22602014-03-10 15:40:16 +010031import android.graphics.Paint;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010032import android.graphics.PointF;
Selim Cinek6811d722016-01-19 17:53:12 -080033import android.graphics.PorterDuff;
34import android.graphics.PorterDuffXfermode;
35import android.graphics.Rect;
Mady Mellor4b80b102016-01-22 08:03:58 -080036import android.os.Handler;
Selim Cinek67b22602014-03-10 15:40:16 +010037import android.util.AttributeSet;
38import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070039import android.util.Pair;
Selim Cinek67b22602014-03-10 15:40:16 +010040import android.view.MotionEvent;
41import android.view.VelocityTracker;
42import android.view.View;
43import android.view.ViewConfiguration;
44import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020045import android.view.ViewTreeObserver;
Selim Cinek572bbd42014-04-25 16:43:27 +020046import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080047import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010048import android.widget.OverScroller;
Jorim Jaggi56306252014-07-03 00:40:09 +020049
Mady Mellora41587b2016-02-11 18:43:06 -080050import com.android.internal.logging.MetricsLogger;
51import com.android.internal.logging.MetricsProto.MetricsEvent;
Selim Cinek67b22602014-03-10 15:40:16 +010052import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080053import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010054import com.android.systemui.R;
55import com.android.systemui.SwipeHelper;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070056import com.android.systemui.classifier.FalsingManager;
Selim Cineka32ab602014-06-11 15:06:01 +020057import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040058import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020059import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010060import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020061import com.android.systemui.statusbar.ExpandableView;
Selim Cinek2cd45df2015-06-09 18:00:07 -070062import com.android.systemui.statusbar.NotificationOverflowContainer;
Mady Mellor4b80b102016-01-22 08:03:58 -080063import com.android.systemui.statusbar.NotificationSettingsIconRow;
64import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010065import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020066import com.android.systemui.statusbar.StatusBarState;
Selim Cinek33223572016-02-19 19:32:22 -080067import com.android.systemui.statusbar.notification.FakeShadowView;
Selim Cinek42357e02016-02-24 18:48:01 -080068import com.android.systemui.statusbar.notification.NotificationUtils;
Selim Cinekb5605e52015-02-20 18:21:41 +010069import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020070import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070071import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070072import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010073import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010074
Selim Cinek572bbd42014-04-25 16:43:27 +020075import java.util.ArrayList;
Selim Cinek33223572016-02-19 19:32:22 -080076import java.util.Collections;
77import java.util.Comparator;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020078import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020079
Selim Cinek67b22602014-03-10 15:40:16 +010080/**
81 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
82 */
83public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020084 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Mady Mellor4b80b102016-01-22 08:03:58 -080085 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
Adrian Roos0bd8a4b2016-03-14 16:21:44 -070086 SettingsIconRowListener, LongPressCancelable {
Selim Cinek67b22602014-03-10 15:40:16 +010087
Selim Cinekd35c2792016-01-21 13:20:57 -080088 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -080089 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +010090 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020091 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
92 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020093 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010094
95 /**
96 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
97 */
98 private static final int INVALID_POINTER = -1;
99
Selim Cinek1408eb52014-06-02 14:45:38 +0200100 private ExpandHelper mExpandHelper;
Selim Cinek33223572016-02-19 19:32:22 -0800101 private NotificationSwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +0200102 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +0100103 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -0800104 private final Paint mBackgroundPaint = new Paint();
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100105
106 /**
107 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
108 * externally from {@link #setStackHeight}
109 */
110 private float mLastSetStackHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100111 private int mOwnScrollY;
112 private int mMaxLayoutHeight;
113
114 private VelocityTracker mVelocityTracker;
115 private OverScroller mScroller;
116 private int mTouchSlop;
117 private int mMinimumVelocity;
118 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100119 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200120 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100121 private boolean mIsBeingDragged;
122 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200123 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +0100124 private int mActivePointerId;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100125 private boolean mTouchIsClick;
126 private float mInitialTouchX;
127 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100128
Selim Cinek67b22602014-03-10 15:40:16 +0100129 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100130 private int mContentHeight;
131 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200132 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100133 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100134 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800135 private int mIncreasedPaddingBetweenElements;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200136 private int mTopPadding;
Selim Cinekd83771e2014-07-04 16:45:31 +0200137 private int mCollapseSecondCardPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100138
139 /**
140 * The algorithm which calculates the properties for our children
141 */
Selim Cinekaf0dc312015-12-15 17:01:44 -0800142 private final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100143
144 /**
145 * The current State this Layout is in
146 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200147 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200148 private AmbientState mAmbientState = new AmbientState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100149 private NotificationGroupManager mGroupManager;
Selim Cinek3776fe02016-02-04 13:32:43 -0800150 private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700151 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
152 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
153 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
154 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
155 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200156 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700157 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
158 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200159 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200160 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200161 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800162 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200163
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200164 /**
165 * The raw amount of the overScroll on the top, which is not rubber-banded.
166 */
167 private float mOverScrolledTopPixels;
168
169 /**
170 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
171 */
172 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200173 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200174 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200175 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100176 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200177 private boolean mNeedsAnimation;
178 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200179 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200180 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400181 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100182 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200183 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200184 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200185 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200186 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200187 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700188 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200189 private boolean mExpandingNotification;
190 private boolean mExpandedInThisMotion;
191 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400192 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200193 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400194 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200195
196 /**
197 * Was the scroller scrolled to the top when the down motion was observed?
198 */
199 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200200 /**
201 * The minimal amount of over scroll which is needed in order to switch to the quick settings
202 * when over scrolling on a expanded card.
203 */
204 private float mMinTopOverScrollToEscape;
205 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700206 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200207 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200208 private boolean mDontReportNextOverScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200209 private boolean mRequestViewResizeAnimationOnLayout;
210 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100211 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700212 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700213
Selim Cinek1408eb52014-06-02 14:45:38 +0200214 /**
215 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
216 * This is needed to avoid scrolling too far after the notification was collapsed in the same
217 * motion.
218 */
219 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400220 private SwipeHelper.LongPressListener mLongPressListener;
Mady Mellor4b80b102016-01-22 08:03:58 -0800221 private GearDisplayedListener mGearDisplayedListener;
222
223 private NotificationSettingsIconRow mCurrIconRow;
224 private View mTranslatingParentView;
225 private View mGearExposedView;
Selim Cinek1408eb52014-06-02 14:45:38 +0200226
227 /**
228 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
229 * animating.
230 */
231 private boolean mOnlyScrollingInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200232 private boolean mInterceptDelegateEnabled;
233 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700234 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700235 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200236 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200237 = new ViewTreeObserver.OnPreDrawListener() {
238 @Override
239 public boolean onPreDraw() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200240 updateChildren();
241 mChildrenUpdateRequested = false;
242 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200243 return true;
244 }
245 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200246 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100247 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100248 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700249 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek0fccc722015-07-29 17:04:36 -0700250 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700251 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
252 = new HashSet<>();
253 private HeadsUpManager mHeadsUpManager;
254 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700255 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200256 private boolean mForceNoOverlappingRendering;
Selim Cinek2cd45df2015-06-09 18:00:07 -0700257 private NotificationOverflowContainer mOverflowContainer;
Selim Cineke0890e52015-06-17 11:17:08 -0700258 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700259 private FalsingManager mFalsingManager;
Selim Cinek6811d722016-01-19 17:53:12 -0800260 private boolean mAnimationRunning;
261 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
262 = new ViewTreeObserver.OnPreDrawListener() {
263 @Override
264 public boolean onPreDraw() {
Selim Cinek614576e2016-01-20 10:54:09 -0800265 // if it needs animation
266 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
267 updateBackground();
268 }
Selim Cinek6811d722016-01-19 17:53:12 -0800269 return true;
270 }
271 };
272 private Rect mBackgroundBounds = new Rect();
Selim Cinek614576e2016-01-20 10:54:09 -0800273 private Rect mStartAnimationRect = new Rect();
274 private Rect mEndAnimationRect = new Rect();
Selim Cinekd35c2792016-01-21 13:20:57 -0800275 private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
Selim Cinek614576e2016-01-20 10:54:09 -0800276 private boolean mAnimateNextBackgroundBottom;
277 private boolean mAnimateNextBackgroundTop;
278 private ObjectAnimator mBottomAnimator = null;
279 private ObjectAnimator mTopAnimator = null;
280 private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
281 private ActivatableNotificationView mLastVisibleBackgroundChild = null;
Selim Cinekd35c2792016-01-21 13:20:57 -0800282 private int mBgColor;
283 private float mDimAmount;
284 private ValueAnimator mDimAnimator;
Selim Cinek33223572016-02-19 19:32:22 -0800285 private ArrayList<ExpandableView> mTmpSortedChildren = new ArrayList<>();
Selim Cinekd35c2792016-01-21 13:20:57 -0800286 private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
287 @Override
288 public void onAnimationEnd(Animator animation) {
289 mDimAnimator = null;
290 }
291 };
292 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
293 = new ValueAnimator.AnimatorUpdateListener() {
294
295 @Override
296 public void onAnimationUpdate(ValueAnimator animation) {
297 setDimAmount((Float) animation.getAnimatedValue());
298 }
299 };
Jason Monk16ac3772016-02-10 15:39:21 -0500300 private ViewGroup mQsContainer;
Selim Cinek33223572016-02-19 19:32:22 -0800301 private boolean mContinuousShadowUpdate;
302 private ViewTreeObserver.OnPreDrawListener mShadowUpdater
303 = new ViewTreeObserver.OnPreDrawListener() {
304
305 @Override
306 public boolean onPreDraw() {
307 updateViewShadows();
308 return true;
309 }
310 };
311 private Comparator<ExpandableView> mViewPositionComparator = new Comparator<ExpandableView>() {
312 @Override
313 public int compare(ExpandableView view, ExpandableView otherView) {
314 float endY = view.getTranslationY() + view.getActualHeight();
315 float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
316 if (endY < otherEndY) {
317 return -1;
318 } else if (endY > otherEndY) {
319 return 1;
320 } else {
321 // The two notifications end at the same location
322 return 0;
323 }
324 }
325 };
Selim Cinek25503252016-03-03 15:31:43 -0800326 private PorterDuffXfermode mSrcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
Selim Cinekcd5b22f2016-03-08 16:15:41 -0800327 private boolean mPulsing;
Selim Cinek67b22602014-03-10 15:40:16 +0100328
329 public NotificationStackScrollLayout(Context context) {
330 this(context, null);
331 }
332
333 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
334 this(context, attrs, 0);
335 }
336
337 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
338 this(context, attrs, defStyleAttr, 0);
339 }
340
341 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
342 int defStyleRes) {
343 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinekd35c2792016-01-21 13:20:57 -0800344 mBgColor = context.getColor(R.color.notification_shade_background_color);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200345 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
346 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
347 mExpandHelper = new ExpandHelper(getContext(), this,
348 minHeight, maxHeight);
349 mExpandHelper.setEventSource(this);
350 mExpandHelper.setScrollAdapter(this);
Mady Mellor4b80b102016-01-22 08:03:58 -0800351 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
Selim Cinek1cf41c12014-08-12 20:06:19 +0200352 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800353 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100354 initView(context);
Selim Cinek6811d722016-01-19 17:53:12 -0800355 setWillNotDraw(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100356 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100357 mDebugPaint = new Paint();
358 mDebugPaint.setColor(0xffff0000);
359 mDebugPaint.setStrokeWidth(2);
360 mDebugPaint.setStyle(Paint.Style.STROKE);
361 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700362 mFalsingManager = FalsingManager.getInstance(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100363 }
364
365 @Override
Mady Mellorb53bc272016-02-11 18:28:23 -0800366 public void onGearTouched(ExpandableNotificationRow row, int x, int y) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800367 if (mLongPressListener != null) {
Mady Mellora41587b2016-02-11 18:43:06 -0800368 MetricsLogger.action(mContext, MetricsEvent.ACTION_TOUCH_GEAR,
369 row.getStatusBarNotification().getPackageName());
Mady Mellorb53bc272016-02-11 18:28:23 -0800370 mLongPressListener.onLongPress(row, x, y);
Mady Mellor4b80b102016-01-22 08:03:58 -0800371 }
372 }
373
374 @Override
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000375 public void onSettingsIconRowReset(NotificationSettingsIconRow row) {
376 mSwipeHelper.setSnappedToGear(false);
377 }
378
379 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100380 protected void onDraw(Canvas canvas) {
Selim Cinekd35c2792016-01-21 13:20:57 -0800381 canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100382 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800383 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100384 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200385 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200386 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200387 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
388 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100389 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
390 y = (int) getLayoutHeight();
391 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200392 y = getHeight() - getEmptyBottomMargin();
393 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100394 }
395 }
396
Selim Cinekd35c2792016-01-21 13:20:57 -0800397 private void updateBackgroundDimming() {
398 float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
399 // We need to manually blend in the background color
400 int scrimColor = mScrimController.getScrimBehindColor();
401 // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
402 float alphaInv = 1 - alpha;
403 int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
404 (int) (Color.red(mBgColor) + alphaInv * Color.red(scrimColor)),
405 (int) (Color.green(mBgColor) + alphaInv * Color.green(scrimColor)),
406 (int) (Color.blue(mBgColor) + alphaInv * Color.blue(scrimColor)));
407 mBackgroundPaint.setColor(color);
408 invalidate();
409 }
410
Selim Cinek67b22602014-03-10 15:40:16 +0100411 private void initView(Context context) {
412 mScroller = new OverScroller(getContext());
413 setFocusable(true);
414 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200415 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100416 final ViewConfiguration configuration = ViewConfiguration.get(context);
417 mTouchSlop = configuration.getScaledTouchSlop();
418 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
419 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100420 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100421 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200422 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100423 mBottomStackPeekSize = context.getResources()
424 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800425 mStackScrollAlgorithm.initView(context);
Selim Cinek61633a82016-01-25 15:54:10 -0800426 mPaddingBetweenElements = Math.max(1, context.getResources()
Selim Cinekcacc6042016-01-21 16:16:41 -0800427 .getDimensionPixelSize(R.dimen.notification_divider_height));
Selim Cinek61633a82016-01-25 15:54:10 -0800428 mIncreasedPaddingBetweenElements = context.getResources()
429 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
Selim Cinek587cbf32016-01-19 11:36:18 -0800430 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
Selim Cinek1408eb52014-06-02 14:45:38 +0200431 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
432 R.dimen.min_top_overscroll_to_qs);
Selim Cineka5eaa602014-05-12 21:27:47 +0200433 }
434
Selim Cinek25503252016-03-03 15:31:43 -0800435 public void setDrawBackgroundAsSrc(boolean asSrc) {
436 mBackgroundPaint.setXfermode(asSrc ? mSrcMode : null);
437 invalidate();
438 }
439
Selim Cinekaef92ef2014-06-06 18:06:04 +0200440 private void notifyHeightChangeListener(ExpandableView view) {
441 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100442 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200443 }
Selim Cinek67b22602014-03-10 15:40:16 +0100444 }
445
446 @Override
447 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
448 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinek924c6122016-01-15 18:24:05 -0800449 measureChildren(widthMeasureSpec, heightMeasureSpec);
Selim Cinek67b22602014-03-10 15:40:16 +0100450 }
451
452 @Override
453 protected void onLayout(boolean changed, int l, int t, int r, int b) {
454
455 // we layout all our children centered on the top
456 float centerX = getWidth() / 2.0f;
457 for (int i = 0; i < getChildCount(); i++) {
458 View child = getChildAt(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100459 if (child.getVisibility() == GONE) {
460 continue;
461 }
Selim Cinek67b22602014-03-10 15:40:16 +0100462 float width = child.getMeasuredWidth();
463 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100464 child.layout((int) (centerX - width / 2.0f),
465 0,
466 (int) (centerX + width / 2.0f),
467 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100468 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200469 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100470 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200471 clampScrollPosition();
Selim Cinekb5605e52015-02-20 18:21:41 +0100472 if (mRequestViewResizeAnimationOnLayout) {
Selim Cinek5bc852a2015-12-21 12:19:09 -0800473 requestAnimationOnViewResize(null);
Selim Cinekb5605e52015-02-20 18:21:41 +0100474 mRequestViewResizeAnimationOnLayout = false;
475 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200476 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -0800477 updateFirstAndLastBackgroundViews();
Selim Cinek67b22602014-03-10 15:40:16 +0100478 }
479
Selim Cinek5bc852a2015-12-21 12:19:09 -0800480 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
481 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200482 mNeedViewResizeAnimation = true;
483 mNeedsAnimation = true;
484 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200485 }
486
Selim Cinekc27437b2014-05-14 10:23:33 +0200487 public void updateSpeedBumpIndex(int newIndex) {
Selim Cinekc27437b2014-05-14 10:23:33 +0200488 mAmbientState.setSpeedBumpIndex(newIndex);
489 }
490
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200491 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
492 mListener = listener;
493 }
494
495 /**
496 * Returns the location the given child is currently rendered at.
497 *
498 * @param child the child to get the location for
Selim Cinekb036ca42015-02-20 15:56:28 +0100499 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200500 */
501 public int getChildLocation(View child) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100502 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200503 if (childViewState == null) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100504 return StackViewState.LOCATION_UNKNOWN;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200505 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100506 if (childViewState.gone) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100507 return StackViewState.LOCATION_GONE;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100508 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200509 return childViewState.location;
510 }
511
Selim Cinek67b22602014-03-10 15:40:16 +0100512 private void setMaxLayoutHeight(int maxLayoutHeight) {
513 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200514 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100515 }
516
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200517 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700518 mAmbientState.setLayoutHeight(getLayoutHeight());
519 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100520 }
521
522 /**
523 * Updates the children views according to the stack scroll algorithm. Call this whenever
524 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
525 */
526 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -0800527 updateScrollStateForAddedChildren();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200528 mAmbientState.setScrollY(mOwnScrollY);
529 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200530 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200531 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100532 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200533 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100534 }
535 }
536
Selim Cinek3776fe02016-02-04 13:32:43 -0800537 private void updateScrollStateForAddedChildren() {
538 if (mChildrenToAddAnimated.isEmpty()) {
539 return;
540 }
541 for (int i = 0; i < getChildCount(); i++) {
542 ExpandableView child = (ExpandableView) getChildAt(i);
543 if (mChildrenToAddAnimated.contains(child)) {
544 int startingPosition = getPositionInLinearLayout(child);
Selim Cinek42357e02016-02-24 18:48:01 -0800545 int padding = child.getIncreasedPaddingAmount() == 1.0f
Selim Cinek3776fe02016-02-04 13:32:43 -0800546 ? mIncreasedPaddingBetweenElements :
547 mPaddingBetweenElements;
548 int childHeight = getIntrinsicHeight(child) + padding;
549 if (startingPosition < mOwnScrollY) {
550 // This child starts off screen, so let's keep it offscreen to keep the others visible
551
552 mOwnScrollY += childHeight;
553 }
554 }
555 }
556 clampScrollPosition();
557 }
558
Selim Cinek319bdc42014-05-01 23:01:58 +0200559 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200560 if (!mChildrenUpdateRequested) {
561 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
562 mChildrenUpdateRequested = true;
563 invalidate();
564 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200565 }
566
Selim Cinek67b22602014-03-10 15:40:16 +0100567 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200568 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100569 }
570
Selim Cinekf7a14c02014-07-07 14:01:46 +0200571 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100572 int scrollRange = getScrollRange();
573 if (scrollRange < mOwnScrollY) {
574 mOwnScrollY = scrollRange;
575 }
576 }
577
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200578 public int getTopPadding() {
579 return mTopPadding;
580 }
581
Selim Cinek1408eb52014-06-02 14:45:38 +0200582 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200583 if (mTopPadding != topPadding) {
584 mTopPadding = topPadding;
585 updateAlgorithmHeightAndPadding();
586 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200587 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200588 mTopPaddingNeedsAnimation = true;
589 mNeedsAnimation = true;
590 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200591 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200592 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200593 }
594 }
595
596 /**
597 * Update the height of the stack to a new height.
598 *
599 * @param height the new height of the stack
600 */
601 public void setStackHeight(float height) {
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100602 mLastSetStackHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200603 setIsExpanded(height > 0.0f);
604 int newStackHeight = (int) height;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -0800605 int minStackHeight = getLayoutMinHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200606 int stackHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700607 float paddingOffset;
Selim Cinek131c1e22015-05-11 19:04:49 -0700608 boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
Selim Cinek31aada42015-12-18 17:51:15 -0800609 int normalUnfoldPositionStart = trackingHeadsUp
610 ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700611 : minStackHeight;
Selim Cinek684a4422015-04-15 16:18:39 -0700612 if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
Jorim Jaggi58bef332014-11-21 18:10:32 +0100613 || getNotGoneChildCount() == 0) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700614 paddingOffset = mTopPaddingOverflow;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200615 stackHeight = newStackHeight;
616 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800617 int translationY;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -0800618 translationY = newStackHeight - normalUnfoldPositionStart;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700619 paddingOffset = translationY - mTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200620 stackHeight = (int) (height - (translationY - mTopPadding));
621 }
622 if (stackHeight != mCurrentStackHeight) {
623 mCurrentStackHeight = stackHeight;
624 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200625 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200626 }
Selim Cinekd2281152015-04-10 14:37:46 -0700627 setStackTranslation(paddingOffset);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700628 }
629
Selim Cinekd2281152015-04-10 14:37:46 -0700630 public float getStackTranslation() {
631 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700632 }
633
Selim Cinekd2281152015-04-10 14:37:46 -0700634 private void setStackTranslation(float stackTranslation) {
635 if (stackTranslation != mStackTranslation) {
636 mStackTranslation = stackTranslation;
637 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700638 requestChildrenUpdate();
639 }
Selim Cinek67b22602014-03-10 15:40:16 +0100640 }
641
642 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100643 * 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 +0100644 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
645 *
646 * @return either the layout height or the externally defined height, whichever is smaller
647 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200648 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100649 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
650 }
651
Selim Cinek816c8e42015-11-19 12:00:45 -0800652 public int getFirstItemMinHeight() {
653 final ExpandableView firstChild = getFirstChildNotGone();
654 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100655 }
656
657 public int getBottomStackPeekSize() {
658 return mBottomStackPeekSize;
659 }
660
Selim Cinekd1ad9ab2016-03-01 17:52:20 -0800661 public int getBottomStackSlowDownHeight() {
662 return mBottomStackSlowDownHeight;
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200663 }
664
Dan Sandler4247a5c2014-07-23 15:58:08 -0400665 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100666 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400667 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100668 }
669
Mady Mellor4b80b102016-01-22 08:03:58 -0800670 public void setGearDisplayedListener(GearDisplayedListener listener) {
671 mGearDisplayedListener = listener;
672 }
673
Jason Monk16ac3772016-02-10 15:39:21 -0500674 public void setQsContainer(ViewGroup qsContainer) {
675 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +0200676 }
677
Selim Cinek67b22602014-03-10 15:40:16 +0100678 public void onChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400679 if (mDismissAllInProgress) {
680 return;
681 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100682 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200683 if (mDragAnimPendingChildren.contains(v)) {
684 // We start the swipe and finish it in the same frame, we don't want any animation
685 // for the drag
686 mDragAnimPendingChildren.remove(v);
687 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200688 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200689 mAmbientState.onDragFinished(v);
Selim Cinek33223572016-02-19 19:32:22 -0800690 updateContinuousShadowDrawing();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700691 if (v instanceof ExpandableNotificationRow) {
692 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
693 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700694 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700695 }
696 }
Selim Cinek2a739342016-03-17 10:28:55 -0700697 performDismiss(v);
Blazej Magnowski72323322015-07-24 11:49:40 -0700698
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700699 mFalsingManager.onNotificationDismissed();
700 if (mFalsingManager.shouldEnforceBouncer()) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700701 mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
702 false /* dismissShade */, true /* afterKeyguardGone */);
703 }
Selim Cinekeb973562014-05-02 17:07:49 +0200704 }
705
Selim Cinek2a739342016-03-17 10:28:55 -0700706 private void performDismiss(View v) {
707 if (v instanceof ExpandableNotificationRow) {
708 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek23c80342016-03-17 18:27:36 -0700709 if (mGroupManager.isOnlyChildInSuppressedGroup(row.getStatusBarNotification())) {
Selim Cinek2a739342016-03-17 10:28:55 -0700710 ExpandableNotificationRow groupSummary =
Selim Cinek23c80342016-03-17 18:27:36 -0700711 mGroupManager.getLogicalGroupSummary(row.getStatusBarNotification());
Selim Cinek2a739342016-03-17 10:28:55 -0700712 if (groupSummary.isClearable()) {
713 performDismiss(groupSummary);
714 }
715 }
716 }
717 final View veto = v.findViewById(R.id.veto);
718 if (veto != null && veto.getVisibility() != View.GONE) {
719 veto.performClick();
720 }
721 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
722 }
723
Selim Cinekeb973562014-05-02 17:07:49 +0200724 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800725 public void onChildSnappedBack(View animView, float targetLeft) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200726 mAmbientState.onDragFinished(animView);
Selim Cinek33223572016-02-19 19:32:22 -0800727 updateContinuousShadowDrawing();
Selim Cinekeb973562014-05-02 17:07:49 +0200728 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200729 if (mAnimationsEnabled) {
730 mSnappedBackChildren.add(animView);
731 mNeedsAnimation = true;
732 }
Selim Cinekeb973562014-05-02 17:07:49 +0200733 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200734 } else {
735 // We start the swipe and snap back in the same frame, we don't want any animation
736 mDragAnimPendingChildren.remove(animView);
737 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800738
Mady Mellor3a5e8dd2016-03-12 00:13:23 +0000739 if (mCurrIconRow != null) {
740 if (targetLeft == 0) {
741 mCurrIconRow.resetState();
742 mCurrIconRow = null;
743 if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
744 mGearExposedView = null;
745 }
746 } else {
747 mSwipeHelper.setSnappedToGear(true);
Mady Mellor4b80b102016-01-22 08:03:58 -0800748 }
749 }
Selim Cinek67b22602014-03-10 15:40:16 +0100750 }
751
Adrian Roos5d9cc662014-05-28 17:08:13 +0200752 @Override
753 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700754 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700755 mScrimController.setTopHeadsUpDragAmount(animView,
756 Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
757 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800758 return true; // Don't fade out the notification
Adrian Roos5d9cc662014-05-28 17:08:13 +0200759 }
760
Selim Cinek67b22602014-03-10 15:40:16 +0100761 public void onBeginDrag(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700762 mFalsingManager.onNotificatonStartDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100763 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200764 mAmbientState.onBeginDrag(v);
Selim Cinek33223572016-02-19 19:32:22 -0800765 updateContinuousShadowDrawing();
Selim Cinek131c1e22015-05-11 19:04:49 -0700766 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200767 mDragAnimPendingChildren.add(v);
768 mNeedsAnimation = true;
769 }
Selim Cinekeb973562014-05-02 17:07:49 +0200770 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100771 }
772
Selim Cinek684a4422015-04-15 16:18:39 -0700773 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700774 if (v instanceof ExpandableNotificationRow) {
775 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700776 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700777 }
778 return false;
779 }
780
781 private boolean isHeadsUp(View v) {
782 if (v instanceof ExpandableNotificationRow) {
783 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
784 return row.isHeadsUp();
785 }
786 return false;
787 }
788
Selim Cinek67b22602014-03-10 15:40:16 +0100789 public void onDragCancelled(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700790 mFalsingManager.onNotificatonStopDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100791 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100792 }
793
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700794 @Override
795 public float getFalsingThresholdFactor() {
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700796 return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700797 }
798
Mady Mellor4b80b102016-01-22 08:03:58 -0800799 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100800 public View getChildAtPosition(MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800801 View child = getChildAtPosition(ev.getX(), ev.getY());
802 if (child instanceof ExpandableNotificationRow) {
803 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
804 ExpandableNotificationRow parent = row.getNotificationParent();
Selim Cinek43d30f02016-03-04 10:51:32 -0800805 if (parent != null && parent.areChildrenExpanded()
806 && (mGearExposedView == parent
807 || (parent.getNotificationChildren().size() == 1
808 && parent.isClearable()))) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800809 // In this case the group is expanded and showing the gear for the
810 // group, further interaction should apply to the group, not any
Selim Cinek43d30f02016-03-04 10:51:32 -0800811 // child notifications so we use the parent of the child. We also do the same
812 // if we only have a single child.
813 child = parent;
Mady Mellor4b80b102016-01-22 08:03:58 -0800814 }
815 }
816 return child;
Selim Cinek67b22602014-03-10 15:40:16 +0100817 }
818
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100819 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
820 getLocationOnScreen(mTempInt2);
821 float localTouchY = touchY - mTempInt2[1];
822
823 ExpandableView closestChild = null;
824 float minDist = Float.MAX_VALUE;
825
826 // find the view closest to the location, accounting for GONE views
827 final int count = getChildCount();
828 for (int childIdx = 0; childIdx < count; childIdx++) {
829 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
830 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800831 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100832 continue;
833 }
834 float childTop = slidingChild.getTranslationY();
835 float top = childTop + slidingChild.getClipTopAmount();
836 float bottom = childTop + slidingChild.getActualHeight();
837
838 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
839 if (dist < minDist) {
840 closestChild = slidingChild;
841 minDist = dist;
842 }
843 }
844 return closestChild;
845 }
846
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200847 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100848 getLocationOnScreen(mTempInt2);
849 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100850 }
851
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200852 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100853 // find the view under the pointer, accounting for GONE views
854 final int count = getChildCount();
855 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200856 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100857 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800858 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100859 continue;
860 }
Selim Cinek89faff12014-06-19 16:29:04 -0700861 float childTop = slidingChild.getTranslationY();
862 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200863 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200864
865 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
866 // camera affordance).
867 int left = 0;
868 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100869
870 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100871 if (slidingChild instanceof ExpandableNotificationRow) {
872 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -0700873 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cinek5bc852a2015-12-21 12:19:09 -0800874 && mHeadsUpManager.getTopEntry().entry.row != row
875 && mGroupManager.getGroupSummary(
876 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
877 != row) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700878 continue;
879 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100880 return row.getViewAtPosition(touchY - childTop);
881 }
Selim Cinek67b22602014-03-10 15:40:16 +0100882 return slidingChild;
883 }
884 }
885 return null;
886 }
887
888 public boolean canChildBeExpanded(View v) {
889 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -0700890 && ((ExpandableNotificationRow) v).isExpandable()
Selim Cinek166f7c42016-03-03 16:21:37 -0800891 && (mIsExpanded || !((ExpandableNotificationRow) v).isPinned());
Selim Cinek67b22602014-03-10 15:40:16 +0100892 }
893
894 public void setUserExpandedChild(View v, boolean userExpanded) {
895 if (v instanceof ExpandableNotificationRow) {
Selim Cinek388df6d2015-10-22 13:25:11 -0700896 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded,
897 true /* allowChildrenExpansion */);
Selim Cinek67b22602014-03-10 15:40:16 +0100898 }
899 }
900
901 public void setUserLockedChild(View v, boolean userLocked) {
902 if (v instanceof ExpandableNotificationRow) {
903 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
904 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200905 removeLongPressCallback();
906 requestDisallowInterceptTouchEvent(true);
907 }
908
909 @Override
910 public void expansionStateChanged(boolean isExpanding) {
911 mExpandingNotification = isExpanding;
912 if (!mExpandedInThisMotion) {
913 mMaxScrollAfterExpand = mOwnScrollY;
914 mExpandedInThisMotion = true;
915 }
916 }
917
918 public void setScrollingEnabled(boolean enable) {
919 mScrollingEnabled = enable;
920 }
921
922 public void setExpandingEnabled(boolean enable) {
923 mExpandHelper.setEnabled(enable);
924 }
925
926 private boolean isScrollingEnabled() {
927 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100928 }
929
Selim Cinek67b22602014-03-10 15:40:16 +0100930 public boolean canChildBeDismissed(View v) {
Selim Cinek9c17b772015-07-07 20:37:09 -0700931 return StackScrollAlgorithm.canChildBeDismissed(v);
Selim Cinek67b22602014-03-10 15:40:16 +0100932 }
933
Selim Cinek19c8c702014-08-25 22:09:19 +0200934 @Override
935 public boolean isAntiFalsingNeeded() {
Selim Cinekcb2b6732014-09-05 16:17:22 +0200936 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +0200937 }
938
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100939 private void setSwipingInProgress(boolean isSwiped) {
940 mSwipingInProgress = isSwiped;
941 if(isSwiped) {
942 requestDisallowInterceptTouchEvent(true);
943 }
Selim Cinek67b22602014-03-10 15:40:16 +0100944 }
945
946 @Override
947 protected void onConfigurationChanged(Configuration newConfig) {
948 super.onConfigurationChanged(newConfig);
949 float densityScale = getResources().getDisplayMetrics().density;
950 mSwipeHelper.setDensityScale(densityScale);
951 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
952 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
953 initView(getContext());
954 }
955
Dan Sandlereceda3d2014-07-21 15:35:01 -0400956 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400957 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
Selim Cinek67b22602014-03-10 15:40:16 +0100958 }
959
960 @Override
961 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200962 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
963 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100964 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +0200965 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +0200966 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200967 if (isCancelOrUp) {
968 mExpandHelper.onlyObserveMovements(false);
969 }
970 boolean wasExpandingBefore = mExpandingNotification;
971 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +0200972 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
973 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200974 dispatchDownEventToScroller(ev);
975 }
976 }
Selim Cinek67b22602014-03-10 15:40:16 +0100977 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700978 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
979 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100980 scrollerWantsIt = onScrollTouch(ev);
981 }
982 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200983 if (!mIsBeingDragged
984 && !mExpandingNotification
985 && !mExpandedInThisMotion
986 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100987 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
988 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200989 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
990 }
991
992 private void dispatchDownEventToScroller(MotionEvent ev) {
993 MotionEvent downEvent = MotionEvent.obtain(ev);
994 downEvent.setAction(MotionEvent.ACTION_DOWN);
995 onScrollTouch(downEvent);
996 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +0100997 }
998
999 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001000 if (!isScrollingEnabled()) {
1001 return false;
1002 }
Jason Monk16ac3772016-02-10 15:39:21 -05001003 if (ev.getY() < mQsContainer.getBottom()) {
1004 return false;
1005 }
Selim Cinek67b22602014-03-10 15:40:16 +01001006 initVelocityTrackerIfNotExists();
1007 mVelocityTracker.addMovement(ev);
1008
1009 final int action = ev.getAction();
1010
1011 switch (action & MotionEvent.ACTION_MASK) {
1012 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +02001013 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +01001014 return false;
1015 }
1016 boolean isBeingDragged = !mScroller.isFinished();
1017 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +01001018
1019 /*
1020 * If being flinged and user touches, stop the fling. isFinished
1021 * will be false if being flinged.
1022 */
1023 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001024 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +01001025 }
1026
1027 // Remember where the motion event started
1028 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001029 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01001030 mActivePointerId = ev.getPointerId(0);
1031 break;
1032 }
1033 case MotionEvent.ACTION_MOVE:
1034 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
1035 if (activePointerIndex == -1) {
1036 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
1037 break;
1038 }
1039
1040 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02001041 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01001042 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001043 final int xDiff = Math.abs(x - mDownX);
1044 final int yDiff = Math.abs(deltaY);
1045 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01001046 setIsBeingDragged(true);
1047 if (deltaY > 0) {
1048 deltaY -= mTouchSlop;
1049 } else {
1050 deltaY += mTouchSlop;
1051 }
1052 }
1053 if (mIsBeingDragged) {
1054 // Scroll to follow the motion event
1055 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001056 int range = getScrollRange();
1057 if (mExpandedInThisMotion) {
1058 range = Math.min(range, mMaxScrollAfterExpand);
1059 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001060
1061 float scrollAmount;
1062 if (deltaY < 0) {
1063 scrollAmount = overScrollDown(deltaY);
1064 } else {
1065 scrollAmount = overScrollUp(deltaY, range);
1066 }
Selim Cinek67b22602014-03-10 15:40:16 +01001067
1068 // Calling overScrollBy will call onOverScrolled, which
1069 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001070 if (scrollAmount != 0.0f) {
1071 // The scrolling motion could not be compensated with the
1072 // existing overScroll, we have to scroll the view
1073 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
1074 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +01001075 }
Selim Cinek67b22602014-03-10 15:40:16 +01001076 }
1077 break;
1078 case MotionEvent.ACTION_UP:
1079 if (mIsBeingDragged) {
1080 final VelocityTracker velocityTracker = mVelocityTracker;
1081 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1082 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1083
Selim Cinek1408eb52014-06-02 14:45:38 +02001084 if (shouldOverScrollFling(initialVelocity)) {
1085 onOverScrollFling(true, initialVelocity);
1086 } else {
1087 if (getChildCount() > 0) {
1088 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
1089 float currentOverScrollTop = getCurrentOverScrollAmount(true);
1090 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
1091 fling(-initialVelocity);
1092 } else {
1093 onOverScrollFling(false, initialVelocity);
1094 }
1095 } else {
1096 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
1097 getScrollRange())) {
1098 postInvalidateOnAnimation();
1099 }
Selim Cinek67b22602014-03-10 15:40:16 +01001100 }
1101 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001102 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001103
1104 mActivePointerId = INVALID_POINTER;
1105 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +01001106 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001107
Selim Cinek67b22602014-03-10 15:40:16 +01001108 break;
1109 case MotionEvent.ACTION_CANCEL:
1110 if (mIsBeingDragged && getChildCount() > 0) {
1111 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1112 postInvalidateOnAnimation();
1113 }
1114 mActivePointerId = INVALID_POINTER;
1115 endDrag();
1116 }
1117 break;
1118 case MotionEvent.ACTION_POINTER_DOWN: {
1119 final int index = ev.getActionIndex();
1120 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +02001121 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +01001122 mActivePointerId = ev.getPointerId(index);
1123 break;
1124 }
1125 case MotionEvent.ACTION_POINTER_UP:
1126 onSecondaryPointerUp(ev);
1127 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +02001128 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +01001129 break;
1130 }
1131 return true;
1132 }
1133
Selim Cinek1408eb52014-06-02 14:45:38 +02001134 private void onOverScrollFling(boolean open, int initialVelocity) {
1135 if (mOverscrollTopChangedListener != null) {
1136 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
1137 }
1138 mDontReportNextOverScroll = true;
1139 setOverScrollAmount(0.0f, true, false);
1140 }
1141
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001142 /**
1143 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1144 *
1145 * @param deltaY The amount to scroll upwards, has to be positive.
1146 * @return The amount of scrolling to be performed by the scroller,
1147 * not handled by the overScroll amount.
1148 */
1149 private float overScrollUp(int deltaY, int range) {
1150 deltaY = Math.max(deltaY, 0);
1151 float currentTopAmount = getCurrentOverScrollAmount(true);
1152 float newTopAmount = currentTopAmount - deltaY;
1153 if (currentTopAmount > 0) {
1154 setOverScrollAmount(newTopAmount, true /* onTop */,
1155 false /* animate */);
1156 }
1157 // Top overScroll might not grab all scrolling motion,
1158 // we have to scroll as well.
1159 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1160 float newScrollY = mOwnScrollY + scrollAmount;
1161 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001162 if (!mExpandedInThisMotion) {
1163 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1164 // We overScroll on the top
1165 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1166 false /* onTop */,
1167 false /* animate */);
1168 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001169 mOwnScrollY = range;
1170 scrollAmount = 0.0f;
1171 }
1172 return scrollAmount;
1173 }
1174
1175 /**
1176 * Perform a scroll downward and adapt the overscroll amounts accordingly
1177 *
1178 * @param deltaY The amount to scroll downwards, has to be negative.
1179 * @return The amount of scrolling to be performed by the scroller,
1180 * not handled by the overScroll amount.
1181 */
1182 private float overScrollDown(int deltaY) {
1183 deltaY = Math.min(deltaY, 0);
1184 float currentBottomAmount = getCurrentOverScrollAmount(false);
1185 float newBottomAmount = currentBottomAmount + deltaY;
1186 if (currentBottomAmount > 0) {
1187 setOverScrollAmount(newBottomAmount, false /* onTop */,
1188 false /* animate */);
1189 }
1190 // Bottom overScroll might not grab all scrolling motion,
1191 // we have to scroll as well.
1192 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1193 float newScrollY = mOwnScrollY + scrollAmount;
1194 if (newScrollY < 0) {
1195 float currentTopPixels = getCurrentOverScrolledPixels(true);
1196 // We overScroll on the top
1197 setOverScrolledPixels(currentTopPixels - newScrollY,
1198 true /* onTop */,
1199 false /* animate */);
1200 mOwnScrollY = 0;
1201 scrollAmount = 0.0f;
1202 }
1203 return scrollAmount;
1204 }
1205
Selim Cinek67b22602014-03-10 15:40:16 +01001206 private void onSecondaryPointerUp(MotionEvent ev) {
1207 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1208 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1209 final int pointerId = ev.getPointerId(pointerIndex);
1210 if (pointerId == mActivePointerId) {
1211 // This was our active pointer going up. Choose a new
1212 // active pointer and adjust accordingly.
1213 // TODO: Make this decision more intelligent.
1214 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1215 mLastMotionY = (int) ev.getY(newPointerIndex);
1216 mActivePointerId = ev.getPointerId(newPointerIndex);
1217 if (mVelocityTracker != null) {
1218 mVelocityTracker.clear();
1219 }
1220 }
1221 }
1222
1223 private void initVelocityTrackerIfNotExists() {
1224 if (mVelocityTracker == null) {
1225 mVelocityTracker = VelocityTracker.obtain();
1226 }
1227 }
1228
1229 private void recycleVelocityTracker() {
1230 if (mVelocityTracker != null) {
1231 mVelocityTracker.recycle();
1232 mVelocityTracker = null;
1233 }
1234 }
1235
1236 private void initOrResetVelocityTracker() {
1237 if (mVelocityTracker == null) {
1238 mVelocityTracker = VelocityTracker.obtain();
1239 } else {
1240 mVelocityTracker.clear();
1241 }
1242 }
1243
1244 @Override
1245 public void computeScroll() {
1246 if (mScroller.computeScrollOffset()) {
1247 // This is called at drawing time by ViewGroup.
1248 int oldX = mScrollX;
1249 int oldY = mOwnScrollY;
1250 int x = mScroller.getCurrX();
1251 int y = mScroller.getCurrY();
1252
1253 if (oldX != x || oldY != y) {
1254 final int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001255 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1256 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001257 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001258 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1259 }
1260 }
Selim Cinek67b22602014-03-10 15:40:16 +01001261
1262 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001263 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001264 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001265 }
1266
1267 // Keep on drawing until the animation has finished.
1268 postInvalidateOnAnimation();
1269 }
1270 }
1271
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001272 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001273 protected boolean overScrollBy(int deltaX, int deltaY,
1274 int scrollX, int scrollY,
1275 int scrollRangeX, int scrollRangeY,
1276 int maxOverScrollX, int maxOverScrollY,
1277 boolean isTouchEvent) {
1278
1279 int newScrollY = scrollY + deltaY;
1280
1281 final int top = -maxOverScrollY;
1282 final int bottom = maxOverScrollY + scrollRangeY;
1283
1284 boolean clampedY = false;
1285 if (newScrollY > bottom) {
1286 newScrollY = bottom;
1287 clampedY = true;
1288 } else if (newScrollY < top) {
1289 newScrollY = top;
1290 clampedY = true;
1291 }
1292
1293 onOverScrolled(0, newScrollY, false, clampedY);
1294
1295 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001296 }
1297
1298 /**
1299 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1300 * overscroll effect based on numPixels. By default this will also cancel animations on the
1301 * same overScroll edge.
1302 *
1303 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1304 * the rubber-banding logic.
1305 * @param onTop Should the effect be applied on top of the scroller.
1306 * @param animate Should an animation be performed.
1307 */
1308 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001309 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001310 }
1311
1312 /**
1313 * Set the effective overScroll amount which will be directly reflected in the layout.
1314 * By default this will also cancel animations on the same overScroll edge.
1315 *
1316 * @param amount The amount to overScroll by.
1317 * @param onTop Should the effect be applied on top of the scroller.
1318 * @param animate Should an animation be performed.
1319 */
1320 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1321 setOverScrollAmount(amount, onTop, animate, true);
1322 }
1323
1324 /**
1325 * Set the effective overScroll amount which will be directly reflected in the layout.
1326 *
1327 * @param amount The amount to overScroll by.
1328 * @param onTop Should the effect be applied on top of the scroller.
1329 * @param animate Should an animation be performed.
1330 * @param cancelAnimators Should running animations be cancelled.
1331 */
1332 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1333 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001334 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1335 }
1336
1337 /**
1338 * Set the effective overScroll amount which will be directly reflected in the layout.
1339 *
1340 * @param amount The amount to overScroll by.
1341 * @param onTop Should the effect be applied on top of the scroller.
1342 * @param animate Should an animation be performed.
1343 * @param cancelAnimators Should running animations be cancelled.
1344 * @param isRubberbanded The value which will be passed to
1345 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1346 */
1347 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1348 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001349 if (cancelAnimators) {
1350 mStateAnimator.cancelOverScrollAnimators(onTop);
1351 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001352 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001353 }
1354
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001355 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1356 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001357 amount = Math.max(0, amount);
1358 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001359 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001360 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001361 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001362 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001363 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001364 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001365 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001366 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001367 }
1368 }
1369
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001370 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001371 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1372 if (mDontReportNextOverScroll) {
1373 mDontReportNextOverScroll = false;
1374 return;
1375 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001376 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001377 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001378 }
1379 }
1380
1381 public void setOverscrollTopChangedListener(
1382 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1383 mOverscrollTopChangedListener = overscrollTopChangedListener;
1384 }
1385
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001386 public float getCurrentOverScrollAmount(boolean top) {
1387 return mAmbientState.getOverScrollAmount(top);
1388 }
1389
1390 public float getCurrentOverScrolledPixels(boolean top) {
1391 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1392 }
1393
1394 private void setOverScrolledPixels(float amount, boolean onTop) {
1395 if (onTop) {
1396 mOverScrolledTopPixels = amount;
1397 } else {
1398 mOverScrolledBottomPixels = amount;
1399 }
1400 }
1401
Selim Cinek319bdc42014-05-01 23:01:58 +02001402 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001403 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001404 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001405 }
1406
1407 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001408 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001409 // Treat animating scrolls differently; see #computeScroll() for why.
1410 if (!mScroller.isFinished()) {
1411 final int oldX = mScrollX;
1412 final int oldY = mOwnScrollY;
1413 mScrollX = scrollX;
1414 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001415 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001416 springBack();
1417 } else {
1418 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1419 invalidateParentIfNeeded();
1420 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001421 float overScrollTop = getCurrentOverScrollAmount(true);
1422 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001423 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001424 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001425 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001426 }
Selim Cinek67b22602014-03-10 15:40:16 +01001427 }
Selim Cinek67b22602014-03-10 15:40:16 +01001428 } else {
1429 customScrollTo(scrollY);
1430 scrollTo(scrollX, mScrollY);
1431 }
1432 }
1433
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001434 private void springBack() {
1435 int scrollRange = getScrollRange();
1436 boolean overScrolledTop = mOwnScrollY <= 0;
1437 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1438 if (overScrolledTop || overScrolledBottom) {
1439 boolean onTop;
1440 float newAmount;
1441 if (overScrolledTop) {
1442 onTop = true;
1443 newAmount = -mOwnScrollY;
1444 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001445 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001446 } else {
1447 onTop = false;
1448 newAmount = mOwnScrollY - scrollRange;
1449 mOwnScrollY = scrollRange;
1450 }
1451 setOverScrollAmount(newAmount, onTop, false);
1452 setOverScrollAmount(0.0f, onTop, true);
1453 mScroller.forceFinished(true);
1454 }
1455 }
1456
Selim Cinek67b22602014-03-10 15:40:16 +01001457 private int getScrollRange() {
1458 int scrollRange = 0;
Jorim Jaggibe565df2014-04-28 17:51:23 +02001459 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001460 if (firstChild != null) {
Selim Cinek67b22602014-03-10 15:40:16 +01001461 int contentHeight = getContentHeight();
Selim Cineka5eaa602014-05-12 21:27:47 +02001462 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1463 + mBottomStackSlowDownHeight);
Selim Cinek4a1ac842014-05-01 15:51:58 +02001464 if (scrollRange > 0) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001465 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
Selim Cinek343e6e22014-04-11 21:23:30 +02001466 // We want to at least be able collapse the first item and not ending in a weird
1467 // end state.
Selim Cinek816c8e42015-11-19 12:00:45 -08001468 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight
1469 - firstChild.getMinHeight());
Selim Cinek343e6e22014-04-11 21:23:30 +02001470 }
Selim Cinek67b22602014-03-10 15:40:16 +01001471 }
1472 return scrollRange;
1473 }
1474
Selim Cinek343e6e22014-04-11 21:23:30 +02001475 /**
1476 * @return the first child which has visibility unequal to GONE
1477 */
Selim Cinekb55386d2015-12-16 17:26:49 -08001478 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02001479 int childCount = getChildCount();
1480 for (int i = 0; i < childCount; i++) {
1481 View child = getChildAt(i);
1482 if (child.getVisibility() != View.GONE) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001483 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02001484 }
1485 }
1486 return null;
1487 }
1488
Selim Cinek4a1ac842014-05-01 15:51:58 +02001489 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001490 * @return The first child which has visibility unequal to GONE which is currently below the
1491 * given translationY or equal to it.
1492 */
1493 private View getFirstChildBelowTranlsationY(float translationY) {
1494 int childCount = getChildCount();
1495 for (int i = 0; i < childCount; i++) {
1496 View child = getChildAt(i);
1497 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1498 return child;
1499 }
1500 }
1501 return null;
1502 }
1503
1504 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001505 * @return the last child which has visibility unequal to GONE
1506 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001507 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001508 int childCount = getChildCount();
1509 for (int i = childCount - 1; i >= 0; i--) {
1510 View child = getChildAt(i);
1511 if (child.getVisibility() != View.GONE) {
1512 return child;
1513 }
1514 }
1515 return null;
1516 }
1517
Jorim Jaggi069cd032014-05-15 03:09:01 +02001518 /**
1519 * @return the number of children which have visibility unequal to GONE
1520 */
1521 public int getNotGoneChildCount() {
1522 int childCount = getChildCount();
1523 int count = 0;
1524 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07001525 ExpandableView child = (ExpandableView) getChildAt(i);
1526 if (child.getVisibility() != View.GONE && !child.willBeGone()) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02001527 count++;
1528 }
1529 }
1530 return count;
1531 }
1532
Selim Cinek343e6e22014-04-11 21:23:30 +02001533 private int getMaxExpandHeight(View view) {
1534 if (view instanceof ExpandableNotificationRow) {
1535 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001536 return row.getIntrinsicHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001537 }
1538 return view.getHeight();
1539 }
1540
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001541 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001542 return mContentHeight;
1543 }
1544
1545 private void updateContentHeight() {
1546 int height = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08001547 float previousIncreasedAmount = 0.0f;
Selim Cinek67b22602014-03-10 15:40:16 +01001548 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08001549 ExpandableView expandableView = (ExpandableView) getChildAt(i);
1550 if (expandableView.getVisibility() != View.GONE) {
Selim Cinek42357e02016-02-24 18:48:01 -08001551 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001552 if (height != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08001553 height += (int) NotificationUtils.interpolate(
1554 mPaddingBetweenElements,
1555 mIncreasedPaddingBetweenElements,
1556 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Jorim Jaggid4a57442014-04-10 02:45:55 +02001557 }
Selim Cinek42357e02016-02-24 18:48:01 -08001558 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08001559 height += expandableView.getIntrinsicHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001560 }
1561 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001562 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001563 }
1564
Selim Cinek6811d722016-01-19 17:53:12 -08001565 private void updateBackground() {
1566 if (mAmbientState.isDark()) {
1567 return;
1568 }
1569 updateBackgroundBounds();
1570 if (!mCurrentBounds.equals(mBackgroundBounds)) {
Selim Cinek614576e2016-01-20 10:54:09 -08001571 if (mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom || areBoundsAnimating()) {
1572 startBackgroundAnimation();
1573 } else {
1574 mCurrentBounds.set(mBackgroundBounds);
1575 applyCurrentBackgroundBounds();
1576 }
1577 } else {
1578 if (mBottomAnimator != null) {
1579 mBottomAnimator.cancel();
1580 }
1581 if (mTopAnimator != null) {
1582 mTopAnimator.cancel();
1583 }
Selim Cinek6811d722016-01-19 17:53:12 -08001584 }
Selim Cinek614576e2016-01-20 10:54:09 -08001585 mAnimateNextBackgroundBottom = false;
1586 mAnimateNextBackgroundTop = false;
1587 }
1588
1589 private boolean areBoundsAnimating() {
1590 return mBottomAnimator != null || mTopAnimator != null;
1591 }
1592
1593 private void startBackgroundAnimation() {
1594 startBottomAnimation();
1595 startTopAnimation();
1596 }
1597
1598 private void startTopAnimation() {
1599 int previousEndValue = mEndAnimationRect.top;
1600 int newEndValue = mBackgroundBounds.top;
1601 ObjectAnimator previousAnimator = mTopAnimator;
1602 if (previousAnimator != null && previousEndValue == newEndValue) {
1603 return;
1604 }
1605 if (!mAnimateNextBackgroundTop) {
1606 // just a local update was performed
1607 if (previousAnimator != null) {
1608 // we need to increase all animation keyframes of the previous animator by the
1609 // relative change to the end value
1610 int previousStartValue = mStartAnimationRect.top;
1611 PropertyValuesHolder[] values = previousAnimator.getValues();
1612 values[0].setIntValues(previousStartValue, newEndValue);
1613 mStartAnimationRect.top = previousStartValue;
1614 mEndAnimationRect.top = newEndValue;
1615 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1616 return;
1617 } else {
1618 // no new animation needed, let's just apply the value
1619 setBackgroundTop(newEndValue);
1620 return;
1621 }
1622 }
1623 if (previousAnimator != null) {
1624 previousAnimator.cancel();
1625 }
1626 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
1627 mCurrentBounds.top, newEndValue);
1628 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1629 animator.setInterpolator(interpolator);
1630 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1631 // remove the tag when the animation is finished
1632 animator.addListener(new AnimatorListenerAdapter() {
1633 @Override
1634 public void onAnimationEnd(Animator animation) {
1635 mStartAnimationRect.top = -1;
1636 mEndAnimationRect.top = -1;
1637 mTopAnimator = null;
1638 }
1639 });
1640 animator.start();
1641 mStartAnimationRect.top = mCurrentBounds.top;
1642 mEndAnimationRect.top = newEndValue;
1643 mTopAnimator = animator;
1644 }
1645
1646 private void startBottomAnimation() {
1647 int previousStartValue = mStartAnimationRect.bottom;
1648 int previousEndValue = mEndAnimationRect.bottom;
1649 int newEndValue = mBackgroundBounds.bottom;
1650 ObjectAnimator previousAnimator = mBottomAnimator;
1651 if (previousAnimator != null && previousEndValue == newEndValue) {
1652 return;
1653 }
1654 if (!mAnimateNextBackgroundBottom) {
1655 // just a local update was performed
1656 if (previousAnimator != null) {
1657 // we need to increase all animation keyframes of the previous animator by the
1658 // relative change to the end value
1659 PropertyValuesHolder[] values = previousAnimator.getValues();
1660 values[0].setIntValues(previousStartValue, newEndValue);
1661 mStartAnimationRect.bottom = previousStartValue;
1662 mEndAnimationRect.bottom = newEndValue;
1663 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1664 return;
1665 } else {
1666 // no new animation needed, let's just apply the value
1667 setBackgroundBottom(newEndValue);
1668 return;
1669 }
1670 }
1671 if (previousAnimator != null) {
1672 previousAnimator.cancel();
1673 }
1674 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
1675 mCurrentBounds.bottom, newEndValue);
1676 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1677 animator.setInterpolator(interpolator);
1678 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1679 // remove the tag when the animation is finished
1680 animator.addListener(new AnimatorListenerAdapter() {
1681 @Override
1682 public void onAnimationEnd(Animator animation) {
1683 mStartAnimationRect.bottom = -1;
1684 mEndAnimationRect.bottom = -1;
1685 mBottomAnimator = null;
1686 }
1687 });
1688 animator.start();
1689 mStartAnimationRect.bottom = mCurrentBounds.bottom;
1690 mEndAnimationRect.bottom = newEndValue;
1691 mBottomAnimator = animator;
1692 }
1693
1694 private void setBackgroundTop(int top) {
1695 mCurrentBounds.top = top;
1696 applyCurrentBackgroundBounds();
1697 }
1698
1699 public void setBackgroundBottom(int bottom) {
1700 mCurrentBounds.bottom = bottom;
1701 applyCurrentBackgroundBounds();
1702 }
1703
1704 private void applyCurrentBackgroundBounds() {
1705 mScrimController.setExcludedBackgroundArea(mCurrentBounds);
Selim Cinek614576e2016-01-20 10:54:09 -08001706 invalidate();
Selim Cinek6811d722016-01-19 17:53:12 -08001707 }
1708
1709 /**
1710 * Update the background bounds to the new desired bounds
1711 */
1712 private void updateBackgroundBounds() {
Selim Cinek614576e2016-01-20 10:54:09 -08001713 mBackgroundBounds.left = (int) getX();
1714 mBackgroundBounds.right = (int) (getX() + getWidth());
1715 if (!mIsExpanded) {
1716 mBackgroundBounds.top = 0;
1717 mBackgroundBounds.bottom = 0;
1718 }
1719 ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001720 int top = 0;
1721 if (firstView != null) {
1722 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView);
Selim Cinek614576e2016-01-20 10:54:09 -08001723 if (mAnimateNextBackgroundTop
1724 || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
1725 || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
Selim Cinek6811d722016-01-19 17:53:12 -08001726 // we're ending up at the same location as we are now, lets just skip the animation
1727 top = finalTranslationY;
1728 } else {
1729 top = (int) firstView.getTranslationY();
1730 }
1731 }
Selim Cinek614576e2016-01-20 10:54:09 -08001732 ActivatableNotificationView lastView = mLastVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001733 int bottom = 0;
1734 if (lastView != null) {
1735 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView);
1736 int finalHeight = StackStateAnimator.getFinalActualHeight(lastView);
1737 int finalBottom = finalTranslationY + finalHeight;
1738 finalBottom = Math.min(finalBottom, getHeight());
Selim Cinek614576e2016-01-20 10:54:09 -08001739 if (mAnimateNextBackgroundBottom
1740 || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
1741 || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
Selim Cinek6811d722016-01-19 17:53:12 -08001742 // we're ending up at the same location as we are now, lets just skip the animation
1743 bottom = finalBottom;
1744 } else {
1745 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
1746 bottom = Math.min(bottom, getHeight());
1747 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001748 } else {
1749 top = (int) (mTopPadding + mStackTranslation);
Selim Cinek7db42982016-02-02 15:21:41 -08001750 bottom = top;
Selim Cinek6811d722016-01-19 17:53:12 -08001751 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001752 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1753 mBackgroundBounds.top = (int) Math.max(mTopPadding + mStackTranslation, top);
1754 } else {
1755 // otherwise the animation from the shade to the keyguard will jump as it's maxed
1756 mBackgroundBounds.top = Math.max(0, top);
1757 }
1758 mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
Selim Cinek6811d722016-01-19 17:53:12 -08001759 }
1760
Selim Cinek614576e2016-01-20 10:54:09 -08001761 private ActivatableNotificationView getFirstPinnedHeadsUp() {
1762 int childCount = getChildCount();
1763 for (int i = 0; i < childCount; i++) {
1764 View child = getChildAt(i);
1765 if (child.getVisibility() != View.GONE
1766 && child instanceof ExpandableNotificationRow) {
1767 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1768 if (row.isPinned()) {
1769 return row;
1770 }
1771 }
1772 }
1773 return null;
1774 }
1775
1776 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001777 int childCount = getChildCount();
1778 for (int i = childCount - 1; i >= 0; i--) {
1779 View child = getChildAt(i);
1780 if (child.getVisibility() != View.GONE
1781 && child instanceof ActivatableNotificationView) {
1782 return (ActivatableNotificationView) child;
1783 }
1784 }
1785 return null;
1786 }
1787
Selim Cinek614576e2016-01-20 10:54:09 -08001788 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001789 int childCount = getChildCount();
1790 for (int i = 0; i < childCount; i++) {
1791 View child = getChildAt(i);
1792 if (child.getVisibility() != View.GONE
1793 && child instanceof ActivatableNotificationView) {
1794 return (ActivatableNotificationView) child;
1795 }
1796 }
1797 return null;
1798 }
1799
Selim Cinek67b22602014-03-10 15:40:16 +01001800 /**
1801 * Fling the scroll view
1802 *
1803 * @param velocityY The initial velocity in the Y direction. Positive
1804 * numbers mean that the finger/cursor is moving down the screen,
1805 * which means we want to scroll towards the top.
1806 */
1807 private void fling(int velocityY) {
1808 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001809 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001810
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001811 float topAmount = getCurrentOverScrollAmount(true);
1812 float bottomAmount = getCurrentOverScrollAmount(false);
1813 if (velocityY < 0 && topAmount > 0) {
1814 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001815 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001816 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001817 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001818 * mOverflingDistance + topAmount;
1819 } else if (velocityY > 0 && bottomAmount > 0) {
1820 mOwnScrollY += bottomAmount;
1821 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001822 mMaxOverScroll = Math.abs(velocityY) / 1000f
1823 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1824 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001825 } else {
1826 // it will be set once we reach the boundary
1827 mMaxOverScroll = 0.0f;
1828 }
Selim Cinek94ab18c2016-02-25 12:35:51 -08001829 int minScrollY = Math.max(0, scrollRange);
1830 if (mExpandedInThisMotion) {
1831 minScrollY = Math.min(minScrollY, mMaxScrollAfterExpand);
1832 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001833 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek94ab18c2016-02-25 12:35:51 -08001834 minScrollY, 0, mExpandedInThisMotion && mOwnScrollY >= 0 ? 0 : Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01001835
1836 postInvalidateOnAnimation();
1837 }
1838 }
1839
Selim Cinek1408eb52014-06-02 14:45:38 +02001840 /**
1841 * @return Whether a fling performed on the top overscroll edge lead to the expanded
1842 * overScroll view (i.e QS).
1843 */
1844 private boolean shouldOverScrollFling(int initialVelocity) {
1845 float topOverScroll = getCurrentOverScrollAmount(true);
1846 return mScrolledToTopOnFirstDown
1847 && !mExpandedInThisMotion
1848 && topOverScroll > mMinTopOverScrollToEscape
1849 && initialVelocity > 0;
1850 }
1851
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001852 /**
1853 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1854 * account.
1855 *
1856 * @param qsHeight the top padding imposed by the quick settings panel
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001857 * @param animate whether to animate the change
1858 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1859 * {@code qsHeight} is the final top padding
1860 */
Jason Monk16ac3772016-02-10 15:39:21 -05001861 public void updateTopPadding(float qsHeight, boolean animate,
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001862 boolean ignoreIntrinsicPadding) {
Jason Monk16ac3772016-02-10 15:39:21 -05001863 float start = qsHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001864 float stackHeight = getHeight() - start;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08001865 int minStackHeight = getLayoutMinHeight();
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001866 if (stackHeight <= minStackHeight) {
1867 float overflow = minStackHeight - stackHeight;
1868 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001869 start = getHeight() - stackHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001870 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02001871 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001872 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001873 }
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001874 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1875 animate);
1876 setStackHeight(mLastSetStackHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02001877 }
1878
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08001879 public int getLayoutMinHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001880 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08001881 int firstChildMinHeight = firstChild != null
1882 ? firstChild.getIntrinsicHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08001883 : mCollapsedSize;
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08001884 if (mOwnScrollY > 0) {
1885 firstChildMinHeight = Math.max(firstChildMinHeight - mOwnScrollY, mCollapsedSize);
1886 }
1887 return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
1888 mMaxLayoutHeight - mTopPadding);
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001889 }
1890
1891 public float getTopPaddingOverflow() {
1892 return mTopPaddingOverflow;
1893 }
1894
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001895 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001896 final ExpandableView firstChild = getFirstChildNotGone();
1897 final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
1898 : mCollapsedSize;
1899 return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08001900 + mBottomStackSlowDownHeight;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001901 }
1902
Selim Cinek1408eb52014-06-02 14:45:38 +02001903 private int clampPadding(int desiredPadding) {
1904 return Math.max(desiredPadding, mIntrinsicPadding);
1905 }
1906
Selim Cinekfed1ab62014-06-17 14:10:33 -07001907 private float getRubberBandFactor(boolean onTop) {
1908 if (!onTop) {
1909 return RUBBER_BAND_FACTOR_NORMAL;
1910 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001911 if (mExpandedInThisMotion) {
1912 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001913 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001914 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1915 } else if (mScrolledToTopOnFirstDown) {
1916 return 1.0f;
1917 }
1918 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02001919 }
1920
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001921 /**
1922 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1923 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1924 * overscroll view (e.g. expand QS).
1925 */
1926 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001927 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001928 || !mScrolledToTopOnFirstDown;
1929 }
1930
Selim Cinek67b22602014-03-10 15:40:16 +01001931 private void endDrag() {
1932 setIsBeingDragged(false);
1933
1934 recycleVelocityTracker();
1935
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001936 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1937 setOverScrollAmount(0, true /* onTop */, true /* animate */);
1938 }
1939 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1940 setOverScrollAmount(0, false /* onTop */, true /* animate */);
1941 }
Selim Cinek67b22602014-03-10 15:40:16 +01001942 }
1943
Jorim Jaggi56306252014-07-03 00:40:09 +02001944 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1945 ev.offsetLocation(sourceView.getX(), sourceView.getY());
1946 ev.offsetLocation(-targetView.getX(), -targetView.getY());
1947 }
1948
Selim Cinek67b22602014-03-10 15:40:16 +01001949 @Override
1950 public boolean onInterceptTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001951 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001952 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001953 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001954 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001955 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1956 }
Selim Cinek67b22602014-03-10 15:40:16 +01001957 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001958 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01001959 scrollWantsIt = onInterceptTouchEventScroll(ev);
1960 }
1961 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001962 if (!mIsBeingDragged
1963 && !mExpandingNotification
1964 && !mExpandedInThisMotion
1965 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001966 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1967 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001968 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1969 }
1970
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001971 private void handleEmptySpaceClick(MotionEvent ev) {
1972 switch (ev.getActionMasked()) {
1973 case MotionEvent.ACTION_MOVE:
1974 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1975 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1976 mTouchIsClick = false;
1977 }
1978 break;
1979 case MotionEvent.ACTION_UP:
1980 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1981 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1982 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1983 }
1984 break;
1985 }
1986 }
1987
Selim Cinek1408eb52014-06-02 14:45:38 +02001988 private void initDownStates(MotionEvent ev) {
1989 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1990 mExpandedInThisMotion = false;
1991 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001992 mDisallowScrollingInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001993 mTouchIsClick = true;
1994 mInitialTouchX = ev.getX();
1995 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001996 }
Selim Cinek67b22602014-03-10 15:40:16 +01001997 }
1998
Selim Cinekef5127e2015-12-21 16:55:58 -08001999 public void setChildTransferInProgress(boolean childTransferInProgress) {
2000 mChildTransferInProgress = childTransferInProgress;
2001 }
2002
Christoph Studer068f5922014-04-08 17:43:07 -04002003 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002004 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04002005 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002006 // we only call our internal methods if this is actually a removal and not just a
2007 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08002008 if (!mChildTransferInProgress) {
Selim Cinekb5605e52015-02-20 18:21:41 +01002009 onViewRemovedInternal(child);
2010 }
2011 }
2012
2013 private void onViewRemovedInternal(View child) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002014 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002015 // This is only a position change, don't do anything special
2016 return;
2017 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02002018 ((ExpandableView) child).setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04002019 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinek61633a82016-01-25 15:54:10 -08002020 updateScrollStateForRemovedChild((ExpandableView) child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07002021 boolean animationGenerated = generateRemoveAnimation(child);
2022 if (animationGenerated && !mSwipedOutViews.contains(child)) {
2023 // Add this view to an overlay in order to ensure that it will still be temporary
2024 // drawn when removed
2025 getOverlay().add(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002026 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002027 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02002028
2029 // Make sure the clipRect we might have set is removed
Selim Cineka272dfe2015-02-20 18:12:28 +01002030 ((ExpandableView) child).setClipTopOptimization(0);
Selim Cinekc27437b2014-05-14 10:23:33 +02002031 }
2032
Selim Cinekb5605e52015-02-20 18:21:41 +01002033 private boolean isChildInGroup(View child) {
2034 return child instanceof ExpandableNotificationRow
2035 && mGroupManager.isChildInGroupWithSummary(
2036 ((ExpandableNotificationRow) child).getStatusBarNotification());
2037 }
2038
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002039 /**
2040 * Generate a remove animation for a child view.
2041 *
2042 * @param child The view to generate the remove animation for.
2043 * @return Whether an animation was generated.
2044 */
2045 private boolean generateRemoveAnimation(View child) {
Selim Cineke0890e52015-06-17 11:17:08 -07002046 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002047 mAddedHeadsUpChildren.remove(child);
2048 return false;
2049 }
Selim Cinek0fccc722015-07-29 17:04:36 -07002050 if (isClickedHeadsUp(child)) {
2051 // An animation is already running, add it to the Overlay
2052 mClearOverlayViewsWhenFinished.add(child);
2053 return true;
2054 }
Selim Cinekb5605e52015-02-20 18:21:41 +01002055 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002056 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02002057 // Generate Animations
2058 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002059 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002060 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02002061 } else {
2062 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002063 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002064 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02002065 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002066 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002067 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002068 }
2069
Selim Cinek0fccc722015-07-29 17:04:36 -07002070 private boolean isClickedHeadsUp(View child) {
2071 return HeadsUpManager.isClickedHeadsUpNotification(child);
2072 }
2073
Selim Cineke0890e52015-06-17 11:17:08 -07002074 /**
2075 * Remove a removed child view from the heads up animations if it was just added there
2076 *
2077 * @return whether any child was removed from the list to animate
2078 */
2079 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2080 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002081 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2082 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07002083 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002084 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07002085 mTmpList.add(eventPair);
2086 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002087 }
2088 }
Selim Cineke0890e52015-06-17 11:17:08 -07002089 if (hasAddEvent) {
2090 // This child was just added lets remove all events.
2091 mHeadsUpChangeAnimations.removeAll(mTmpList);
2092 }
2093 mTmpList.clear();
2094 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002095 }
2096
Selim Cinek572bbd42014-04-25 16:43:27 +02002097 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01002098 * @param child the child to query
2099 * @return whether a view is not a top level child but a child notification and that group is
2100 * not expanded
2101 */
2102 private boolean isChildInInvisibleGroup(View child) {
2103 if (child instanceof ExpandableNotificationRow) {
2104 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2105 ExpandableNotificationRow groupSummary =
2106 mGroupManager.getGroupSummary(row.getStatusBarNotification());
2107 if (groupSummary != null && groupSummary != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07002108 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01002109 }
2110 }
2111 return false;
2112 }
2113
2114 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02002115 * Updates the scroll position when a child was removed
2116 *
2117 * @param removedChild the removed child
2118 */
Selim Cinek61633a82016-01-25 15:54:10 -08002119 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002120 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinek42357e02016-02-24 18:48:01 -08002121 int padding = (int) NotificationUtils.interpolate(
2122 mPaddingBetweenElements,
2123 mIncreasedPaddingBetweenElements,
2124 removedChild.getIncreasedPaddingAmount());
Selim Cinek61633a82016-01-25 15:54:10 -08002125 int childHeight = getIntrinsicHeight(removedChild) + padding;
Selim Cinek572bbd42014-04-25 16:43:27 +02002126 int endPosition = startingPosition + childHeight;
2127 if (endPosition <= mOwnScrollY) {
2128 // This child is fully scrolled of the top, so we have to deduct its height from the
2129 // scrollPosition
2130 mOwnScrollY -= childHeight;
2131 } else if (startingPosition < mOwnScrollY) {
2132 // This child is currently being scrolled into, set the scroll position to the start of
2133 // this child
2134 mOwnScrollY = startingPosition;
2135 }
2136 }
2137
Selim Cinekd7c4e002014-07-04 18:36:42 +02002138 private int getIntrinsicHeight(View view) {
2139 if (view instanceof ExpandableView) {
2140 ExpandableView expandableView = (ExpandableView) view;
2141 return expandableView.getIntrinsicHeight();
2142 }
2143 return view.getHeight();
2144 }
2145
Selim Cinek572bbd42014-04-25 16:43:27 +02002146 private int getPositionInLinearLayout(View requestedChild) {
2147 int position = 0;
Selim Cinek42357e02016-02-24 18:48:01 -08002148 float previousIncreasedAmount = 0.0f;
Selim Cinek572bbd42014-04-25 16:43:27 +02002149 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002150 ExpandableView child = (ExpandableView) getChildAt(i);
2151 boolean notGone = child.getVisibility() != View.GONE;
2152 if (notGone) {
Selim Cinek42357e02016-02-24 18:48:01 -08002153 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
Selim Cinek61633a82016-01-25 15:54:10 -08002154 if (position != 0) {
Selim Cinek42357e02016-02-24 18:48:01 -08002155 position += (int) NotificationUtils.interpolate(
2156 mPaddingBetweenElements,
2157 mIncreasedPaddingBetweenElements,
2158 Math.max(previousIncreasedAmount, increasedPaddingAmount));
Selim Cinek61633a82016-01-25 15:54:10 -08002159 }
Selim Cinek42357e02016-02-24 18:48:01 -08002160 previousIncreasedAmount = increasedPaddingAmount;
Selim Cinek61633a82016-01-25 15:54:10 -08002161 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002162 if (child == requestedChild) {
2163 return position;
2164 }
Selim Cinek61633a82016-01-25 15:54:10 -08002165 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02002166 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002167 }
2168 }
2169 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02002170 }
2171
2172 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002173 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02002174 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002175 onViewAddedInternal(child);
2176 }
2177
Selim Cinek614576e2016-01-20 10:54:09 -08002178 private void updateFirstAndLastBackgroundViews() {
2179 ActivatableNotificationView firstChild = getFirstChildWithBackground();
2180 ActivatableNotificationView lastChild = getLastChildWithBackground();
2181 if (mAnimationsEnabled && mIsExpanded) {
2182 mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
2183 mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
2184 } else {
2185 mAnimateNextBackgroundTop = false;
2186 mAnimateNextBackgroundBottom = false;
2187 }
2188 mFirstVisibleBackgroundChild = firstChild;
2189 mLastVisibleBackgroundChild = lastChild;
2190 }
2191
Selim Cinekb5605e52015-02-20 18:21:41 +01002192 private void onViewAddedInternal(View child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002193 updateHideSensitiveForChild(child);
Jorim Jaggibe565df2014-04-28 17:51:23 +02002194 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00002195 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002196 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02002197 updateChronometerForChild(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002198 }
2199
Selim Cinekd06c41c2015-07-06 14:51:36 -07002200 private void updateHideSensitiveForChild(View child) {
2201 if (mAmbientState.isHideSensitive() && child instanceof ExpandableView) {
2202 ExpandableView expandableView = (ExpandableView) child;
2203 expandableView.setHideSensitiveForIntrinsicHeight(true);
2204 }
2205 }
2206
Selim Cinekb5605e52015-02-20 18:21:41 +01002207 public void notifyGroupChildRemoved(View row) {
2208 onViewRemovedInternal(row);
2209 }
2210
2211 public void notifyGroupChildAdded(View row) {
2212 onViewAddedInternal(row);
2213 }
2214
Jorim Jaggi75c95042014-05-16 19:09:59 +02002215 public void setAnimationsEnabled(boolean animationsEnabled) {
2216 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002217 updateNotificationAnimationStates();
2218 }
2219
2220 private void updateNotificationAnimationStates() {
Selim Cinekcd5b22f2016-03-08 16:15:41 -08002221 boolean running = mAnimationsEnabled || mPulsing;
Selim Cinekcab4a602014-09-03 14:47:57 +02002222 int childCount = getChildCount();
2223 for (int i = 0; i < childCount; i++) {
2224 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07002225 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02002226 updateAnimationState(running, child);
2227 }
2228 }
2229
Selim Cinek51ae05d2014-09-09 15:51:38 +02002230 private void updateAnimationState(View child) {
Selim Cinekcd5b22f2016-03-08 16:15:41 -08002231 updateAnimationState((mAnimationsEnabled || mPulsing)
2232 && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002233 }
2234
2235
Selim Cinekcab4a602014-09-03 14:47:57 +02002236 private void updateAnimationState(boolean running, View child) {
2237 if (child instanceof ExpandableNotificationRow) {
2238 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2239 row.setIconAnimationRunning(running);
2240 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02002241 }
2242
2243 public boolean isAddOrRemoveAnimationPending() {
2244 return mNeedsAnimation
2245 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
2246 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002247 /**
2248 * Generate an animation for an added child view.
2249 *
2250 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00002251 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002252 */
Jorim Jaggif6411742014-08-05 17:10:43 +00002253 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002254 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002255 // Generate Animations
2256 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00002257 if (fromMoreCard) {
2258 mFromMoreCardAdditions.add(child);
2259 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002260 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02002261 }
Adrian Roos777ef562015-12-01 17:37:14 -08002262 if (isHeadsUp(child) && !mChangePositionInProgress) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002263 mAddedHeadsUpChildren.add(child);
2264 mChildrenToAddAnimated.remove(child);
2265 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002266 }
2267
2268 /**
2269 * Change the position of child to a new location
2270 *
2271 * @param child the view to change the position for
2272 * @param newIndex the new index
2273 */
2274 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002275 int currentIndex = indexOfChild(child);
2276 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002277 mChangePositionInProgress = true;
Adrian Roos14503e22016-03-09 14:01:24 -08002278 ((ExpandableView)child).setChangingPosition(true);
Selim Cinekc27437b2014-05-14 10:23:33 +02002279 removeView(child);
2280 addView(child, newIndex);
Adrian Roos14503e22016-03-09 14:01:24 -08002281 ((ExpandableView)child).setChangingPosition(false);
Selim Cinek159ffdb2014-06-04 22:24:18 +02002282 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04002283 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002284 mChildrenChangingPositions.add(child);
2285 mNeedsAnimation = true;
2286 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002287 }
2288 }
2289
Selim Cinekf4c19962014-05-01 21:55:31 +02002290 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002291 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002292 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002293 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002294 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002295 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002296 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
2297 mGoToFullShadeDelay);
Selim Cinek614576e2016-01-20 10:54:09 -08002298 setAnimationRunning(true);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002299 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08002300 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08002301 updateViewShadows();
Selim Cinekf4c19962014-05-01 21:55:31 +02002302 } else {
2303 applyCurrentState();
2304 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002305 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02002306 }
2307
2308 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07002309 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02002310 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002311 generateChildAdditionEvents();
2312 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02002313 generateSnapBackEvents();
2314 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002315 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002316 generateActivateEvent();
2317 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02002318 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04002319 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002320 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02002321 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01002322 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02002323 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002324 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002325 }
2326
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002327 private void generateHeadsUpAnimationEvents() {
2328 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002329 ExpandableNotificationRow row = eventPair.first;
2330 boolean isHeadsUp = eventPair.second;
2331 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
2332 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07002333 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07002334 if (!mIsExpanded && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07002335 type = row.wasJustClicked()
2336 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
2337 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinekeaee9c02015-06-25 11:04:20 -04002338 } else {
2339 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
2340 if (viewState == null) {
2341 // A view state was never generated for this view, so we don't need to animate
2342 // this. This may happen with notification children.
2343 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07002344 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04002345 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
2346 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
2347 // Our custom add animation
2348 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
2349 } else {
2350 // Normal add animation
2351 type = AnimationEvent.ANIMATION_TYPE_ADD;
2352 }
2353 onBottom = !pinnedAndClosed;
2354 }
Selim Cineka59ecc32015-04-07 10:51:49 -07002355 }
2356 AnimationEvent event = new AnimationEvent(row, type);
2357 event.headsUpFromBottom = onBottom;
2358 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002359 }
2360 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07002361 mAddedHeadsUpChildren.clear();
2362 }
2363
Selim Cinekeaee9c02015-06-25 11:04:20 -04002364 private boolean shouldHunAppearFromBottom(StackViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002365 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
2366 return false;
2367 }
2368 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002369 }
2370
Selim Cinekb5605e52015-02-20 18:21:41 +01002371 private void generateGroupExpansionEvent() {
2372 // Generate a group expansion/collapsing event if there is such a group at all
2373 if (mExpandedGroupView != null) {
2374 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
2375 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
2376 mExpandedGroupView = null;
2377 }
2378 }
2379
Selim Cineka5e211b2014-08-11 17:35:48 +02002380 private void generateViewResizeEvent() {
2381 if (mNeedViewResizeAnimation) {
2382 mAnimationEvents.add(
2383 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
2384 }
2385 mNeedViewResizeAnimation = false;
2386 }
2387
Selim Cinekeb973562014-05-02 17:07:49 +02002388 private void generateSnapBackEvents() {
2389 for (View child : mSnappedBackChildren) {
2390 mAnimationEvents.add(new AnimationEvent(child,
2391 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
2392 }
2393 mSnappedBackChildren.clear();
2394 }
2395
2396 private void generateDragEvents() {
2397 for (View child : mDragAnimPendingChildren) {
2398 mAnimationEvents.add(new AnimationEvent(child,
2399 AnimationEvent.ANIMATION_TYPE_START_DRAG));
2400 }
2401 mDragAnimPendingChildren.clear();
2402 }
2403
Selim Cinek572bbd42014-04-25 16:43:27 +02002404 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002405 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002406 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
2407 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002408 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
2409 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002410 AnimationEvent event = new AnimationEvent(child, animationType);
2411
2412 // we need to know the view after this one
2413 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
2414 mAnimationEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +02002415 }
2416 mSwipedOutViews.clear();
2417 mChildrenToRemoveAnimated.clear();
2418 }
2419
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002420 private void generatePositionChangeEvents() {
2421 for (View child : mChildrenChangingPositions) {
2422 mAnimationEvents.add(new AnimationEvent(child,
2423 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2424 }
2425 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01002426 if (mGenerateChildOrderChangedEvent) {
2427 mAnimationEvents.add(new AnimationEvent(null,
2428 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2429 mGenerateChildOrderChangedEvent = false;
2430 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002431 }
2432
Selim Cinek572bbd42014-04-25 16:43:27 +02002433 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002434 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002435 if (mFromMoreCardAdditions.contains(child)) {
2436 mAnimationEvents.add(new AnimationEvent(child,
2437 AnimationEvent.ANIMATION_TYPE_ADD,
2438 StackStateAnimator.ANIMATION_DURATION_STANDARD));
2439 } else {
2440 mAnimationEvents.add(new AnimationEvent(child,
2441 AnimationEvent.ANIMATION_TYPE_ADD));
2442 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002443 }
2444 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002445 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04002446 }
2447
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002448 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02002449 if (mTopPaddingNeedsAnimation) {
2450 mAnimationEvents.add(
2451 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2452 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002453 mTopPaddingNeedsAnimation = false;
2454 }
2455
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002456 private void generateActivateEvent() {
2457 if (mActivateNeedsAnimation) {
2458 mAnimationEvents.add(
2459 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2460 }
2461 mActivateNeedsAnimation = false;
2462 }
2463
Selim Cinekd9acca52014-09-01 22:33:25 +02002464 private void generateAnimateEverythingEvent() {
2465 if (mEverythingNeedsAnimation) {
2466 mAnimationEvents.add(
2467 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2468 }
2469 mEverythingNeedsAnimation = false;
2470 }
2471
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002472 private void generateDimmedEvent() {
2473 if (mDimmedNeedsAnimation) {
2474 mAnimationEvents.add(
2475 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2476 }
2477 mDimmedNeedsAnimation = false;
2478 }
2479
Jorim Jaggiae441282014-08-01 02:45:18 +02002480 private void generateHideSensitiveEvent() {
2481 if (mHideSensitiveNeedsAnimation) {
2482 mAnimationEvents.add(
2483 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2484 }
2485 mHideSensitiveNeedsAnimation = false;
2486 }
2487
John Spurlockbf370992014-06-17 13:58:31 -04002488 private void generateDarkEvent() {
2489 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002490 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2491 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2492 mAnimationEvents.add(ev);
John Spurlockbf370992014-06-17 13:58:31 -04002493 }
2494 mDarkNeedsAnimation = false;
2495 }
2496
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002497 private void generateGoToFullShadeEvent() {
2498 if (mGoToFullShadeNeedsAnimation) {
2499 mAnimationEvents.add(
2500 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2501 }
2502 mGoToFullShadeNeedsAnimation = false;
2503 }
2504
Selim Cinek67b22602014-03-10 15:40:16 +01002505 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002506 if (!isScrollingEnabled()) {
2507 return false;
2508 }
Selim Cinek67b22602014-03-10 15:40:16 +01002509 /*
2510 * This method JUST determines whether we want to intercept the motion.
2511 * If we return true, onMotionEvent will be called and we do the actual
2512 * scrolling there.
2513 */
2514
2515 /*
2516 * Shortcut the most recurring case: the user is in the dragging
Chris Wren5d53df42015-06-26 11:26:03 -04002517 * state and is moving their finger. We want to intercept this
Selim Cinek67b22602014-03-10 15:40:16 +01002518 * motion.
2519 */
2520 final int action = ev.getAction();
2521 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2522 return true;
2523 }
2524
Selim Cinek67b22602014-03-10 15:40:16 +01002525 switch (action & MotionEvent.ACTION_MASK) {
2526 case MotionEvent.ACTION_MOVE: {
2527 /*
2528 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04002529 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01002530 */
2531
2532 /*
2533 * Locally do absolute value. mLastMotionY is set to the y value
2534 * of the down event.
2535 */
2536 final int activePointerId = mActivePointerId;
2537 if (activePointerId == INVALID_POINTER) {
2538 // If we don't have a valid id, the touch down wasn't on content.
2539 break;
2540 }
2541
2542 final int pointerIndex = ev.findPointerIndex(activePointerId);
2543 if (pointerIndex == -1) {
2544 Log.e(TAG, "Invalid pointerId=" + activePointerId
2545 + " in onInterceptTouchEvent");
2546 break;
2547 }
2548
2549 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02002550 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01002551 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02002552 final int xDiff = Math.abs(x - mDownX);
2553 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01002554 setIsBeingDragged(true);
2555 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002556 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01002557 initVelocityTrackerIfNotExists();
2558 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01002559 }
2560 break;
2561 }
2562
2563 case MotionEvent.ACTION_DOWN: {
2564 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05302565 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01002566 if (getChildAtPosition(ev.getX(), y) == null) {
2567 setIsBeingDragged(false);
2568 recycleVelocityTracker();
2569 break;
2570 }
2571
2572 /*
2573 * Remember location of down touch.
2574 * ACTION_DOWN always refers to pointer index 0.
2575 */
2576 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002577 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01002578 mActivePointerId = ev.getPointerId(0);
2579
2580 initOrResetVelocityTracker();
2581 mVelocityTracker.addMovement(ev);
2582 /*
2583 * If being flinged and user touches the screen, initiate drag;
2584 * otherwise don't. mScroller.isFinished should be false when
2585 * being flinged.
2586 */
2587 boolean isBeingDragged = !mScroller.isFinished();
2588 setIsBeingDragged(isBeingDragged);
2589 break;
2590 }
2591
2592 case MotionEvent.ACTION_CANCEL:
2593 case MotionEvent.ACTION_UP:
2594 /* Release the drag */
2595 setIsBeingDragged(false);
2596 mActivePointerId = INVALID_POINTER;
2597 recycleVelocityTracker();
2598 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2599 postInvalidateOnAnimation();
2600 }
2601 break;
2602 case MotionEvent.ACTION_POINTER_UP:
2603 onSecondaryPointerUp(ev);
2604 break;
2605 }
2606
2607 /*
2608 * The only time we want to intercept motion events is if we are in the
2609 * drag mode.
2610 */
2611 return mIsBeingDragged;
2612 }
2613
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002614 /**
2615 * @return Whether the specified motion event is actually happening over the content.
2616 */
2617 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01002618 return isInContentBounds(event.getY());
2619 }
2620
2621 /**
2622 * @return Whether a y coordinate is inside the content.
2623 */
2624 public boolean isInContentBounds(float y) {
2625 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002626 }
2627
Selim Cinek67b22602014-03-10 15:40:16 +01002628 private void setIsBeingDragged(boolean isDragged) {
2629 mIsBeingDragged = isDragged;
2630 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002631 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02002632 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002633 }
2634 }
2635
2636 @Override
2637 public void onWindowFocusChanged(boolean hasWindowFocus) {
2638 super.onWindowFocusChanged(hasWindowFocus);
2639 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002640 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002641 }
2642 }
Selim Cinekfab078b2014-03-27 22:45:58 +01002643
Adrian Roos0bd8a4b2016-03-14 16:21:44 -07002644 @Override
2645 public void requestDisallowLongPress() {
2646 removeLongPressCallback();
2647 }
2648
Selim Cinek1408eb52014-06-02 14:45:38 +02002649 public void removeLongPressCallback() {
2650 mSwipeHelper.removeLongPressCallback();
2651 }
2652
Selim Cinekfab078b2014-03-27 22:45:58 +01002653 @Override
2654 public boolean isScrolledToTop() {
2655 return mOwnScrollY == 0;
2656 }
2657
2658 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002659 public boolean isScrolledToBottom() {
2660 return mOwnScrollY >= getScrollRange();
2661 }
2662
2663 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01002664 public View getHostView() {
2665 return this;
2666 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002667
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002668 public int getEmptyBottomMargin() {
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08002669 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize
2670 - mBottomStackSlowDownHeight;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002671 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002672 }
2673
Selim Cinek5f71bee2015-11-18 10:25:23 -08002674 public float getKeyguardBottomStackSize() {
2675 return mBottomStackPeekSize + getResources().getDimensionPixelSize(
2676 R.dimen.bottom_stack_slow_down_length);
2677 }
2678
Selim Cinek1685e632014-04-08 02:27:49 +02002679 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002680 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02002681 }
2682
2683 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002684 mIsExpansionChanging = false;
Selim Cinek4fe3e472014-07-03 16:32:54 +02002685 if (!mIsExpanded) {
2686 mOwnScrollY = 0;
Selim Cinek78f40082016-03-10 18:06:27 -08002687 mPhoneStatusBar.resetUserExpandedStates();
Selim Cinekf336f4c2014-11-12 16:58:16 +01002688
2689 // lets make sure nothing is in the overlay anymore
2690 getOverlay().clear();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002691 }
Selim Cinek1685e632014-04-08 02:27:49 +02002692 }
2693
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002694 public void onPanelTrackingStarted() {
2695 mPanelTracking = true;
2696 }
2697 public void onPanelTrackingStopped() {
2698 mPanelTracking = false;
2699 }
2700
Selim Cinekb24e0a92015-06-09 20:17:30 -07002701 public void resetScrollPosition() {
2702 mScroller.abortAnimation();
2703 mOwnScrollY = 0;
2704 }
2705
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02002706 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02002707 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02002708 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02002709 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002710 if (changed) {
Selim Cinek9184f9c2016-02-02 17:36:53 -08002711 if (!mIsExpanded) {
2712 mGroupManager.collapseAllGroups();
Dan Sandlercb012892016-03-01 00:45:06 -05002713 } else {
2714 // XXX: HACK: we should not be clearing notification effects from way down here.
2715 // But at the moment we don't have a reliable way to know when the window is
2716 // actually exposed to the air, so
2717 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
2718 if (DEBUG) {
2719 Log.v(TAG, "clearing notification effects from scroller");
2720 }
2721 mPhoneStatusBar.clearNotificationEffects();
2722 }
Selim Cinek9184f9c2016-02-02 17:36:53 -08002723 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002724 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02002725 updateChronometers();
2726 }
2727 }
2728
2729 private void updateChronometers() {
2730 int childCount = getChildCount();
2731 for (int i = 0; i < childCount; i++) {
2732 updateChronometerForChild(getChildAt(i));
2733 }
2734 }
2735
2736 private void updateChronometerForChild(View child) {
2737 if (child instanceof ExpandableNotificationRow) {
2738 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2739 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002740 }
Selim Cinek1685e632014-04-08 02:27:49 +02002741 }
2742
Jorim Jaggibe565df2014-04-28 17:51:23 +02002743 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01002744 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002745 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002746 updateScrollPositionOnExpandInBottom(view);
2747 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02002748 notifyHeightChangeListener(view);
Selim Cinekb5605e52015-02-20 18:21:41 +01002749 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08002750 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
2751 ? (ExpandableNotificationRow) view
2752 : null;
2753 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01002754 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002755 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02002756 }
2757
Selim Cineka5e211b2014-08-11 17:35:48 +02002758 @Override
2759 public void onReset(ExpandableView view) {
Selim Cinek0e41dea2014-08-25 13:55:06 +02002760 if (mIsExpanded && mAnimationsEnabled) {
2761 mRequestViewResizeAnimationOnLayout = true;
2762 }
Selim Cinek51ae05d2014-09-09 15:51:38 +02002763 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02002764 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02002765 }
2766
Selim Cinekf7a14c02014-07-07 14:01:46 +02002767 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2768 if (view instanceof ExpandableNotificationRow) {
2769 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002770 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08002771 if (row.isSummaryWithChildren()) {
2772 return;
2773 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02002774 // We are actually expanding this view
Selim Cinek4e4cac32016-03-11 16:45:52 -08002775 float endPosition = row.getTranslationY() + row.getActualHeight();
Selim Cinek388df6d2015-10-22 13:25:11 -07002776 if (row.isChildInGroup()) {
Selim Cinek4e4cac32016-03-11 16:45:52 -08002777 endPosition += row.getNotificationParent().getTranslationY();
Selim Cinek388df6d2015-10-22 13:25:11 -07002778 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02002779 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
Selim Cinekcb9400a2015-06-03 16:56:13 +02002780 mBottomStackSlowDownHeight + (int) mStackTranslation;
Selim Cinekf7a14c02014-07-07 14:01:46 +02002781 if (endPosition > stackEnd) {
2782 mOwnScrollY += endPosition - stackEnd;
2783 mDisallowScrollingInThisMotion = true;
2784 }
2785 }
2786 }
2787 }
2788
Jorim Jaggibe565df2014-04-28 17:51:23 +02002789 public void setOnHeightChangedListener(
2790 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2791 this.mOnHeightChangedListener = mOnHeightChangedListener;
2792 }
2793
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002794 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2795 mOnEmptySpaceClickListener = listener;
2796 }
2797
Selim Cinek572bbd42014-04-25 16:43:27 +02002798 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08002799 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02002800 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07002801 runAnimationFinishedRunnables();
Selim Cinek0fccc722015-07-29 17:04:36 -07002802 clearViewOverlays();
2803 }
2804
2805 private void clearViewOverlays() {
2806 for (View view : mClearOverlayViewsWhenFinished) {
2807 getOverlay().remove(view);
2808 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002809 }
2810
2811 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002812 for (Runnable runnable : mAnimationFinishedRunnables) {
2813 runnable.run();
2814 }
2815 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002816 }
2817
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002818 /**
2819 * See {@link AmbientState#setDimmed}.
2820 */
2821 public void setDimmed(boolean dimmed, boolean animate) {
2822 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002823 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002824 mDimmedNeedsAnimation = true;
2825 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08002826 animateDimmed(dimmed);
2827 } else {
2828 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002829 }
2830 requestChildrenUpdate();
2831 }
2832
Selim Cinekd35c2792016-01-21 13:20:57 -08002833 private void setDimAmount(float dimAmount) {
2834 mDimAmount = dimAmount;
2835 updateBackgroundDimming();
2836 }
2837
2838 private void animateDimmed(boolean dimmed) {
2839 if (mDimAnimator != null) {
2840 mDimAnimator.cancel();
2841 }
2842 float target = dimmed ? 1.0f : 0.0f;
2843 if (target == mDimAmount) {
2844 return;
2845 }
2846 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
2847 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
2848 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
2849 mDimAnimator.addListener(mDimEndListener);
2850 mDimAnimator.addUpdateListener(mDimUpdateListener);
2851 mDimAnimator.start();
2852 }
2853
Jorim Jaggiae441282014-08-01 02:45:18 +02002854 public void setHideSensitive(boolean hideSensitive, boolean animate) {
2855 if (hideSensitive != mAmbientState.isHideSensitive()) {
2856 int childCount = getChildCount();
2857 for (int i = 0; i < childCount; i++) {
2858 ExpandableView v = (ExpandableView) getChildAt(i);
2859 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2860 }
2861 mAmbientState.setHideSensitive(hideSensitive);
2862 if (animate && mAnimationsEnabled) {
2863 mHideSensitiveNeedsAnimation = true;
2864 mNeedsAnimation = true;
2865 }
2866 requestChildrenUpdate();
2867 }
2868 }
2869
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002870 /**
2871 * See {@link AmbientState#setActivatedChild}.
2872 */
Selim Cineka32ab602014-06-11 15:06:01 +02002873 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002874 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002875 if (mAnimationsEnabled) {
2876 mActivateNeedsAnimation = true;
2877 mNeedsAnimation = true;
2878 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002879 requestChildrenUpdate();
2880 }
2881
Selim Cineka32ab602014-06-11 15:06:01 +02002882 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002883 return mAmbientState.getActivatedChild();
2884 }
2885
Selim Cinek572bbd42014-04-25 16:43:27 +02002886 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02002887 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02002888 if (mListener != null) {
2889 mListener.onChildLocationsChanged(this);
2890 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002891 runAnimationFinishedRunnables();
Selim Cinek6811d722016-01-19 17:53:12 -08002892 updateBackground();
Selim Cinek33223572016-02-19 19:32:22 -08002893 updateViewShadows();
2894 }
2895
2896 private void updateViewShadows() {
2897 // we need to work around an issue where the shadow would not cast between siblings when
2898 // their z difference is between 0 and 0.1
2899
2900 // Lefts first sort by Z difference
2901 for (int i = 0; i < getChildCount(); i++) {
2902 ExpandableView child = (ExpandableView) getChildAt(i);
2903 if (child.getVisibility() != GONE) {
2904 mTmpSortedChildren.add(child);
2905 }
2906 }
2907 Collections.sort(mTmpSortedChildren, mViewPositionComparator);
2908
2909 // Now lets update the shadow for the views
2910 ExpandableView previous = null;
2911 for (int i = 0; i < mTmpSortedChildren.size(); i++) {
2912 ExpandableView expandableView = mTmpSortedChildren.get(i);
2913 float translationZ = expandableView.getTranslationZ();
2914 float otherZ = previous == null ? translationZ : previous.getTranslationZ();
2915 float diff = otherZ - translationZ;
2916 if (diff <= 0.0f || diff >= FakeShadowView.SHADOW_SIBLING_TRESHOLD) {
2917 // There is no fake shadow to be drawn
2918 expandableView.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
2919 } else {
2920 float yLocation = previous.getTranslationY() + previous.getActualHeight() -
2921 expandableView.getTranslationY();
2922 expandableView.setFakeShadowIntensity(diff / FakeShadowView.SHADOW_SIBLING_TRESHOLD,
2923 previous.getOutlineAlpha(), (int) yLocation,
2924 previous.getOutlineTranslation());
2925 }
2926 previous = expandableView;
2927 }
2928
2929 mTmpSortedChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002930 }
2931
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002932 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002933 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002934 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002935 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002936 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002937 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002938 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02002939 }
2940
Selim Cinek1408eb52014-06-02 14:45:38 +02002941 public void cancelExpandHelper() {
2942 mExpandHelper.cancel();
2943 }
2944
2945 public void setIntrinsicPadding(int intrinsicPadding) {
2946 mIntrinsicPadding = intrinsicPadding;
2947 }
2948
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002949 public int getIntrinsicPadding() {
2950 return mIntrinsicPadding;
2951 }
2952
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002953 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02002954 * @return the y position of the first notification
2955 */
2956 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07002957 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02002958 }
2959
Selim Cinekc0ce82d2014-06-10 13:21:15 +02002960 @Override
2961 public boolean shouldDelayChildPressedState() {
2962 return true;
2963 }
2964
Jorim Jaggi457cc352014-06-02 22:47:42 +02002965 /**
John Spurlockbf370992014-06-17 13:58:31 -04002966 * See {@link AmbientState#setDark}.
2967 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002968 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04002969 mAmbientState.setDark(dark);
2970 if (animate && mAnimationsEnabled) {
2971 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002972 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04002973 mNeedsAnimation = true;
2974 }
2975 requestChildrenUpdate();
Selim Cinek6811d722016-01-19 17:53:12 -08002976 if (dark) {
2977 setWillNotDraw(!DEBUG);
2978 mScrimController.setExcludedBackgroundArea(null);
2979 } else {
2980 updateBackground();
2981 setWillNotDraw(false);
2982 // TODO: fade in background
2983 }
John Spurlockbf370992014-06-17 13:58:31 -04002984 }
2985
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002986 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
2987 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
2988 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2989 }
2990 if (screenLocation.y > getBottomMostNotificationBottom()) {
2991 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
2992 }
2993 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
2994 if (child != null) {
2995 return getNotGoneIndex(child);
2996 } else {
2997 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2998 }
2999 }
3000
3001 private int getNotGoneIndex(View child) {
3002 int count = getChildCount();
3003 int notGoneIndex = 0;
3004 for (int i = 0; i < count; i++) {
3005 View v = getChildAt(i);
3006 if (child == v) {
3007 return notGoneIndex;
3008 }
3009 if (v.getVisibility() != View.GONE) {
3010 notGoneIndex++;
3011 }
3012 }
3013 return -1;
3014 }
3015
Dan Sandlereceda3d2014-07-21 15:35:01 -04003016 public void setDismissView(DismissView dismissView) {
Selim Cinek01af3342016-02-09 19:25:31 -08003017 int index = -1;
3018 if (mDismissView != null) {
3019 index = indexOfChild(mDismissView);
3020 removeView(mDismissView);
3021 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003022 mDismissView = dismissView;
Selim Cinek01af3342016-02-09 19:25:31 -08003023 addView(mDismissView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003024 }
3025
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003026 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08003027 int index = -1;
3028 if (mEmptyShadeView != null) {
3029 index = indexOfChild(mEmptyShadeView);
3030 removeView(mEmptyShadeView);
3031 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003032 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08003033 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003034 }
3035
3036 public void updateEmptyShadeView(boolean visible) {
3037 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
3038 int newVisibility = visible ? VISIBLE : GONE;
3039 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003040 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003041 if (mEmptyShadeView.willBeGone()) {
3042 mEmptyShadeView.cancelAnimation();
3043 } else {
3044 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003045 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003046 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003047 mEmptyShadeView.setWillBeGone(false);
3048 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003049 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003050 } else {
Selim Cinek20867102014-12-10 17:09:17 +01003051 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003052 @Override
3053 public void run() {
3054 mEmptyShadeView.setVisibility(GONE);
3055 mEmptyShadeView.setWillBeGone(false);
3056 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07003057 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003058 }
Selim Cinek20867102014-12-10 17:09:17 +01003059 };
3060 if (mAnimationsEnabled) {
3061 mEmptyShadeView.setWillBeGone(true);
3062 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
3063 } else {
3064 mEmptyShadeView.setInvisible();
3065 onFinishedRunnable.run();
3066 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02003067 }
3068 }
3069 }
3070
Selim Cinek2cd45df2015-06-09 18:00:07 -07003071 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
3072 mOverflowContainer = overFlowContainer;
3073 addView(mOverflowContainer);
3074 }
3075
3076 public void updateOverflowContainerVisibility(boolean visible) {
3077 int oldVisibility = mOverflowContainer.willBeGone() ? GONE
3078 : mOverflowContainer.getVisibility();
3079 final int newVisibility = visible ? VISIBLE : GONE;
3080 if (oldVisibility != newVisibility) {
3081 Runnable onFinishedRunnable = new Runnable() {
3082 @Override
3083 public void run() {
3084 mOverflowContainer.setVisibility(newVisibility);
3085 mOverflowContainer.setWillBeGone(false);
3086 updateContentHeight();
3087 notifyHeightChangeListener(mOverflowContainer);
3088 }
3089 };
3090 if (!mAnimationsEnabled || !mIsExpanded) {
3091 mOverflowContainer.cancelAppearDrawing();
3092 onFinishedRunnable.run();
3093 } else if (newVisibility != GONE) {
3094 mOverflowContainer.performAddAnimation(0,
3095 StackStateAnimator.ANIMATION_DURATION_STANDARD);
3096 mOverflowContainer.setVisibility(newVisibility);
3097 mOverflowContainer.setWillBeGone(false);
3098 updateContentHeight();
3099 notifyHeightChangeListener(mOverflowContainer);
3100 } else {
3101 mOverflowContainer.performRemoveAnimation(
3102 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3103 0.0f,
3104 onFinishedRunnable);
3105 mOverflowContainer.setWillBeGone(true);
3106 }
3107 }
3108 }
3109
Dan Sandlereceda3d2014-07-21 15:35:01 -04003110 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003111 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003112 int newVisibility = visible ? VISIBLE : GONE;
3113 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003114 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003115 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003116 mDismissView.cancelAnimation();
3117 } else {
3118 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003119 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003120 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003121 mDismissView.setWillBeGone(false);
3122 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003123 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003124 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003125 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003126 @Override
3127 public void run() {
3128 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003129 mDismissView.setWillBeGone(false);
3130 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003131 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003132 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01003133 };
Selim Cinek20867102014-12-10 17:09:17 +01003134 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003135 mDismissView.setWillBeGone(true);
3136 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
3137 } else {
3138 dimissHideFinishRunnable.run();
Selim Cinek7d5f3742014-11-07 18:07:49 +01003139 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003140 }
3141 }
3142 }
3143
3144 public void setDismissAllInProgress(boolean dismissAllInProgress) {
3145 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07003146 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cineka272dfe2015-02-20 18:12:28 +01003147 if (dismissAllInProgress) {
3148 disableClipOptimization();
3149 }
Selim Cinek9c17b772015-07-07 20:37:09 -07003150 handleDismissAllClipping();
Mady Mellor3cb40242016-02-22 10:43:35 -08003151 if (mCurrIconRow != null && mCurrIconRow.isVisible()) {
Mady Mellor4b80b102016-01-22 08:03:58 -08003152 mCurrIconRow.getNotificationParent().animateTranslateNotification(0 /* left target */);
3153 }
Selim Cinek9c17b772015-07-07 20:37:09 -07003154 }
3155
3156 private void handleDismissAllClipping() {
3157 final int count = getChildCount();
3158 boolean previousChildWillBeDismissed = false;
3159 for (int i = 0; i < count; i++) {
3160 ExpandableView child = (ExpandableView) getChildAt(i);
3161 if (child.getVisibility() == GONE) {
3162 continue;
3163 }
3164 if (mDismissAllInProgress && previousChildWillBeDismissed) {
3165 child.setMinClipTopAmount(child.getClipTopAmount());
3166 } else {
3167 child.setMinClipTopAmount(0);
3168 }
3169 previousChildWillBeDismissed = canChildBeDismissed(child);
3170 }
Selim Cineka272dfe2015-02-20 18:12:28 +01003171 }
3172
3173 private void disableClipOptimization() {
3174 final int count = getChildCount();
3175 for (int i = 0; i < count; i++) {
3176 ExpandableView child = (ExpandableView) getChildAt(i);
3177 if (child.getVisibility() == GONE) {
3178 continue;
3179 }
3180 child.setClipTopOptimization(0);
3181 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003182 }
3183
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003184 public boolean isDismissViewNotGone() {
3185 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
3186 }
3187
3188 public boolean isDismissViewVisible() {
3189 return mDismissView.isVisible();
3190 }
3191
3192 public int getDismissViewHeight() {
Selim Cinekd1ad9ab2016-03-01 17:52:20 -08003193 return mDismissView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003194 }
3195
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01003196 public int getEmptyShadeViewHeight() {
3197 return mEmptyShadeView.getHeight();
3198 }
3199
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003200 public float getBottomMostNotificationBottom() {
3201 final int count = getChildCount();
3202 float max = 0;
3203 for (int childIdx = 0; childIdx < count; childIdx++) {
3204 ExpandableView child = (ExpandableView) getChildAt(childIdx);
3205 if (child.getVisibility() == GONE) {
3206 continue;
3207 }
3208 float bottom = child.getTranslationY() + child.getActualHeight();
3209 if (bottom > max) {
3210 max = bottom;
3211 }
3212 }
Selim Cinekd2281152015-04-10 14:37:46 -07003213 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003214 }
3215
Selim Cinek19c8c702014-08-25 22:09:19 +02003216 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
3217 this.mPhoneStatusBar = phoneStatusBar;
3218 }
3219
Selim Cinekb5605e52015-02-20 18:21:41 +01003220 public void setGroupManager(NotificationGroupManager groupManager) {
3221 this.mGroupManager = groupManager;
3222 }
3223
Selim Cinekd9acca52014-09-01 22:33:25 +02003224 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01003225 requestAnimateEverything();
3226 }
3227
3228 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02003229 if (mIsExpanded && mAnimationsEnabled) {
3230 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01003231 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02003232 requestChildrenUpdate();
3233 }
3234 }
3235
Selim Cinek04fb2582015-06-02 19:58:09 +02003236 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01003237 int childCount = getChildCount();
3238 for (int i = childCount - 1; i >= 0; i--) {
3239 ExpandableView child = (ExpandableView) getChildAt(i);
3240 if (child.getVisibility() != View.GONE) {
3241 float childTop = child.getY();
3242 if (childTop > touchY) {
3243 // we are above a notification entirely let's abort
3244 return false;
3245 }
3246 boolean belowChild = touchY > childTop + child.getActualHeight();
3247 if (child == mDismissView) {
3248 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
3249 touchY - childTop)) {
3250 // We clicked on the dismiss button
3251 return false;
3252 }
3253 } else if (child == mEmptyShadeView) {
3254 // We arrived at the empty shade view, for which we accept all clicks
3255 return true;
3256 } else if (!belowChild){
3257 // We are on a child
3258 return false;
3259 }
3260 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003261 }
Selim Cinek04fb2582015-06-02 19:58:09 +02003262 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003263 }
3264
Selim Cinekb5605e52015-02-20 18:21:41 +01003265 @Override
3266 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08003267 boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
Selim Cinekb5605e52015-02-20 18:21:41 +01003268 if (animated) {
3269 mExpandedGroupView = changedRow;
3270 mNeedsAnimation = true;
3271 }
3272 changedRow.setChildrenExpanded(expanded, animated);
3273 onHeightChanged(changedRow, false /* needsAnimation */);
3274 }
3275
3276 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01003277 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
Selim Cinekef5127e2015-12-21 16:55:58 -08003278 mPhoneStatusBar.requestNotificationUpdate();
3279 }
3280
3281 @Override
Selim Cinek2a739342016-03-17 10:28:55 -07003282 public void onGroupsChanged() {
Selim Cinekef5127e2015-12-21 16:55:58 -08003283 mPhoneStatusBar.requestNotificationUpdate();
Selim Cinekb5605e52015-02-20 18:21:41 +01003284 }
3285
3286 public void generateChildOrderChangedEvent() {
3287 if (mIsExpanded && mAnimationsEnabled) {
3288 mGenerateChildOrderChangedEvent = true;
3289 mNeedsAnimation = true;
3290 requestChildrenUpdate();
3291 }
3292 }
3293
Selim Cinek684a4422015-04-15 16:18:39 -07003294 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003295 mAnimationFinishedRunnables.add(runnable);
3296 }
3297
3298 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
3299 mHeadsUpManager = headsUpManager;
3300 mAmbientState.setHeadsUpManager(headsUpManager);
3301 }
3302
3303 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
3304 if (mAnimationsEnabled) {
3305 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
3306 mNeedsAnimation = true;
3307 requestChildrenUpdate();
3308 }
3309 }
3310
3311 public void setShadeExpanded(boolean shadeExpanded) {
3312 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07003313 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003314 }
3315
Selim Cineka59ecc32015-04-07 10:51:49 -07003316 /**
3317 * Set the boundary for the bottom heads up position. The heads up will always be above this
3318 * position.
3319 *
3320 * @param height the height of the screen
3321 * @param bottomBarHeight the height of the bar on the bottom
3322 */
3323 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
3324 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
3325 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003326 requestChildrenUpdate();
3327 }
3328
3329 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
3330 mTrackingHeadsUp = trackingHeadsUp;
3331 }
3332
Selim Cinekaac93252015-04-14 20:04:12 -07003333 public void setScrimController(ScrimController scrimController) {
3334 mScrimController = scrimController;
Selim Cinekd35c2792016-01-21 13:20:57 -08003335 mScrimController.setScrimBehindChangeRunnable(new Runnable() {
3336 @Override
3337 public void run() {
3338 updateBackgroundDimming();
3339 }
3340 });
Selim Cinekaac93252015-04-14 20:04:12 -07003341 }
3342
Selim Cinekbbc580b2015-06-03 14:11:03 +02003343 public void forceNoOverlappingRendering(boolean force) {
3344 mForceNoOverlappingRendering = force;
3345 }
3346
3347 @Override
3348 public boolean hasOverlappingRendering() {
3349 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
3350 }
3351
Selim Cinek6811d722016-01-19 17:53:12 -08003352 public void setAnimationRunning(boolean animationRunning) {
3353 if (animationRunning != mAnimationRunning) {
3354 if (animationRunning) {
3355 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
3356 } else {
3357 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
3358 }
3359 mAnimationRunning = animationRunning;
Selim Cinek33223572016-02-19 19:32:22 -08003360 updateContinuousShadowDrawing();
Selim Cinek6811d722016-01-19 17:53:12 -08003361 }
3362 }
3363
Selim Cinek3776fe02016-02-04 13:32:43 -08003364 public boolean isExpanded() {
3365 return mIsExpanded;
3366 }
3367
Selim Cinekcd5b22f2016-03-08 16:15:41 -08003368 public void setPulsing(boolean pulsing) {
3369 mPulsing = pulsing;
3370 updateNotificationAnimationStates();
3371 }
3372
Selim Cinek3afd00e2014-08-11 22:32:57 +02003373 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003374 * A listener that is notified when some child locations might have changed.
3375 */
3376 public interface OnChildLocationsChangedListener {
3377 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
3378 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003379
Jorim Jaggi290600a2014-05-30 17:02:20 +02003380 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003381 * A listener that is notified when the empty space below the notifications is clicked on
3382 */
3383 public interface OnEmptySpaceClickListener {
3384 public void onEmptySpaceClicked(float x, float y);
3385 }
3386
3387 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02003388 * A listener that gets notified when the overscroll at the top has changed.
3389 */
3390 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02003391
3392 /**
3393 * Notifies a listener that the overscroll has changed.
3394 *
3395 * @param amount the amount of overscroll, in pixels
3396 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
3397 * unrubberbanded motion to directly expand overscroll view (e.g expand
3398 * QS)
3399 */
3400 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02003401
3402 /**
3403 * Notify a listener that the scroller wants to escape from the scrolling motion and
3404 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
3405 *
3406 * @param velocity The velocity that the Scroller had when over flinging
3407 * @param open Should the fling open or close the overscroll view.
3408 */
3409 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02003410 }
3411
Mady Mellor4b80b102016-01-22 08:03:58 -08003412 /**
3413 * A listener that is notified when the gear is shown behind a notification.
3414 */
3415 public interface GearDisplayedListener {
3416 void onGearDisplayed(ExpandableNotificationRow row);
3417 }
3418
3419 private class NotificationSwipeHelper extends SwipeHelper {
Mady Mellor4b80b102016-01-22 08:03:58 -08003420 private static final long GEAR_SHOW_DELAY = 60;
Mady Mellor4b80b102016-01-22 08:03:58 -08003421 private CheckForDrag mCheckForDrag;
3422 private Handler mHandler;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003423 private boolean mGearSnappedTo;
3424 private boolean mGearSnappedOnLeft;
Mady Mellor4b80b102016-01-22 08:03:58 -08003425
3426 public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
3427 super(swipeDirection, callback, context);
3428 mHandler = new Handler();
3429 }
3430
3431 @Override
3432 public void onDownUpdate(View currView) {
3433 // Set the active view
3434 mTranslatingParentView = currView;
3435
3436 // Reset check for drag gesture
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003437 cancelCheckForDrag();
3438 if (mCurrIconRow != null) {
3439 mCurrIconRow.setSnapping(false);
3440 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003441 mCheckForDrag = null;
Mady Mellor34958fa2016-02-23 09:52:17 -08003442 mCurrIconRow = null;
Mady Mellor4b80b102016-01-22 08:03:58 -08003443
3444 // Slide back any notifications that might be showing a gear
3445 resetExposedGearView();
3446
3447 if (currView instanceof ExpandableNotificationRow) {
3448 // Set the listener for the current row's gear
3449 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
3450 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
Mady Mellor4b80b102016-01-22 08:03:58 -08003451 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003452 }
3453
3454 @Override
3455 public void onMoveUpdate(View view, float translation, float delta) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003456 if (mCurrIconRow != null) {
3457 mCurrIconRow.setSnapping(false); // If we're moving, we're not snapping.
3458
3459 // If the gear is visible and the movement is towards it it's not a location change.
3460 boolean onLeft = mGearSnappedTo ? mGearSnappedOnLeft : mCurrIconRow.isIconOnLeft();
3461 boolean locationChange = isTowardsGear(translation, onLeft)
3462 ? false : mCurrIconRow.isIconLocationChange(translation);
3463 if (locationChange) {
3464 // Don't consider it "snapped" if location has changed.
3465 setSnappedToGear(false);
3466
3467 // Changed directions, make sure we check to fade in icon again.
3468 if (!mHandler.hasCallbacks(mCheckForDrag)) {
3469 // No check scheduled, set null to schedule a new one.
3470 mCheckForDrag = null;
3471 } else {
3472 // Check scheduled, reset alpha and update location; check will fade it in
3473 mCurrIconRow.setGearAlpha(0f);
3474 mCurrIconRow.setIconLocation(translation > 0 /* onLeft */);
3475 }
3476 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003477 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003478
Mady Mellor34958fa2016-02-23 09:52:17 -08003479 final boolean gutsExposed = (view instanceof ExpandableNotificationRow)
3480 && ((ExpandableNotificationRow) view).areGutsExposed();
3481
3482 if (!isPinnedHeadsUp(view) && !gutsExposed) {
3483 // Only show the gear if we're not a heads up view and guts aren't exposed.
3484 checkForDrag();
Mady Mellor4b80b102016-01-22 08:03:58 -08003485 }
3486 }
3487
3488 @Override
3489 public void dismissChild(final View view, float velocity) {
Mady Mellor88eca2aa2016-03-11 23:36:41 +00003490 super.dismissChild(view, velocity);
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003491 cancelCheckForDrag();
3492 setSnappedToGear(false);
Mady Mellor4b80b102016-01-22 08:03:58 -08003493 }
3494
3495 @Override
3496 public void snapChild(final View animView, final float targetLeft, float velocity) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003497 super.snapChild(animView, targetLeft, velocity);
3498 onDragCancelled(animView);
3499 if (targetLeft == 0) {
3500 cancelCheckForDrag();
3501 setSnappedToGear(false);
3502 }
3503 }
3504
3505
3506 @Override
3507 public boolean handleUpEvent(MotionEvent ev, View animView, float velocity,
3508 float translation) {
3509 if (mCurrIconRow == null) {
3510 cancelCheckForDrag();
3511 return false; // Let SwipeHelper handle it.
3512 }
3513
3514 boolean gestureTowardsGear = isTowardsGear(velocity, mCurrIconRow.isIconOnLeft());
3515 boolean gestureFastEnough = Math.abs(velocity) > getEscapeVelocity();
3516
3517 if (mGearSnappedTo && mCurrIconRow.isVisible()) {
3518 if (mGearSnappedOnLeft == mCurrIconRow.isIconOnLeft()) {
3519 boolean coveringGear =
3520 Math.abs(getTranslation(animView)) <= getSpaceForGear(animView) * 0.6f;
3521 if (gestureTowardsGear || coveringGear) {
3522 // Gesture is towards or covering the gear
3523 snapChild(animView, 0 /* leftTarget */, velocity);
3524 } else if (isDismissGesture(ev)) {
3525 // Gesture is a dismiss that's not towards the gear
3526 dismissChild(animView, swipedFastEnough() ? velocity : 0f);
3527 } else {
3528 // Didn't move enough to dismiss or cover, snap to the gear
3529 snapToGear(animView, velocity);
3530 }
3531 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
3532 || (gestureTowardsGear && !swipedFarEnough())) {
3533 // The gear has been snapped to previously, however, the gear is now on the
3534 // other side. If gesture is towards gear and not too far snap to the gear.
3535 snapToGear(animView, velocity);
3536 } else {
3537 dismissOrSnapBack(animView, velocity, ev);
3538 }
3539 } else if ((!gestureFastEnough && swipedEnoughToShowGear(animView))
3540 || gestureTowardsGear) {
3541 // Gear has not been snapped to previously and this is gear revealing gesture
3542 snapToGear(animView, velocity);
3543 } else {
3544 dismissOrSnapBack(animView, velocity, ev);
3545 }
3546 return true;
3547 }
3548
3549 private void dismissOrSnapBack(View animView, float velocity, MotionEvent ev) {
3550 if (isDismissGesture(ev)) {
3551 dismissChild(animView, swipedFastEnough() ? velocity : 0f);
3552 } else {
3553 snapChild(animView, 0 /* leftTarget */, velocity);
3554 }
3555 }
3556
3557 private void snapToGear(View animView, float velocity) {
3558 final float snapBackThreshold = getSpaceForGear(animView);
3559 final float target = mCurrIconRow.isIconOnLeft() ? snapBackThreshold
3560 : -snapBackThreshold;
3561 mGearExposedView = mTranslatingParentView;
3562 if (mGearDisplayedListener != null
3563 && (animView instanceof ExpandableNotificationRow)) {
3564 mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView);
3565 }
3566 if (mCurrIconRow != null) {
3567 mCurrIconRow.setSnapping(true);
3568 setSnappedToGear(true);
3569 }
3570 onDragCancelled(animView);
3571 super.snapChild(animView, target, velocity);
3572 }
3573
3574 private boolean swipedEnoughToShowGear(View animView) {
Mady Mellor4b80b102016-01-22 08:03:58 -08003575 final float snapBackThreshold = getSpaceForGear(animView);
3576 final float translation = getTranslation(animView);
3577 final boolean fromLeft = translation > 0;
3578 final float absTrans = Math.abs(translation);
3579 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
3580
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003581 // If the notification can't be dismissed then how far it can move is
3582 // restricted -- reduce the distance it needs to move in this case.
3583 final float multiplier = canChildBeDismissed(animView) ? 0.4f : 0.2f;
3584 return absTrans >= snapBackThreshold * 0.4f && absTrans <= notiThreshold;
Mady Mellor4b80b102016-01-22 08:03:58 -08003585 }
3586
3587 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -08003588 public Animator getViewTranslationAnimator(View v, float target,
3589 AnimatorUpdateListener listener) {
3590 if (mDismissAllInProgress) {
3591 // When dismissing all, we translate the entire view instead.
3592 return super.getViewTranslationAnimator(v, target, listener);
Mady Mellor34958fa2016-02-23 09:52:17 -08003593 } else if (v instanceof ExpandableNotificationRow) {
3594 return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
3595 } else {
3596 return super.getViewTranslationAnimator(v, target, listener);
Mady Mellor4b80b102016-01-22 08:03:58 -08003597 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003598 }
3599
3600 @Override
3601 public void setTranslation(View v, float translate) {
3602 if (mDismissAllInProgress) {
3603 // When dismissing all, we translate the entire view instead.
3604 super.setTranslation(v, translate);
Mady Mellor34958fa2016-02-23 09:52:17 -08003605 } else {
3606 ((ExpandableView) v).setTranslation(translate);
Mady Mellor4b80b102016-01-22 08:03:58 -08003607 }
3608 }
3609
3610 @Override
3611 public float getTranslation(View v) {
3612 if (mDismissAllInProgress) {
3613 // When dismissing all, we translate the entire view instead.
3614 return super.getTranslation(v);
Mady Mellor34958fa2016-02-23 09:52:17 -08003615 } else {
3616 return ((ExpandableView) v).getTranslation();
Mady Mellor4b80b102016-01-22 08:03:58 -08003617 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003618 }
3619
Mady Mellor4b80b102016-01-22 08:03:58 -08003620 /**
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003621 * Returns whether the gesture is towards the gear location or not.
3622 */
3623 private boolean isTowardsGear(float velocity, boolean onLeft) {
3624 if (mCurrIconRow == null) {
3625 return false;
3626 }
3627 return mCurrIconRow.isVisible()
3628 && ((onLeft && velocity <= 0) || (!onLeft && velocity >= 0));
3629 }
3630
3631 /**
3632 * Indicates the the gear has been snapped to.
3633 */
3634 private void setSnappedToGear(boolean snapped) {
3635 mGearSnappedOnLeft = (mCurrIconRow != null) ? mCurrIconRow.isIconOnLeft() : false;
3636 mGearSnappedTo = snapped && mCurrIconRow != null;
3637 }
3638
3639 /**
Mady Mellor4b80b102016-01-22 08:03:58 -08003640 * Returns the horizontal space in pixels required to display the gear behind a
3641 * notification.
3642 */
3643 private float getSpaceForGear(View view) {
3644 if (view instanceof ExpandableNotificationRow) {
3645 return ((ExpandableNotificationRow) view).getSpaceForGear();
3646 }
3647 return 0;
3648 }
3649
3650 private void checkForDrag() {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003651 if (mCheckForDrag == null || !mHandler.hasCallbacks(mCheckForDrag)) {
Mady Mellor4b80b102016-01-22 08:03:58 -08003652 mCheckForDrag = new CheckForDrag();
3653 mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY);
3654 }
3655 }
3656
3657 private void cancelCheckForDrag() {
3658 if (mCurrIconRow != null) {
3659 mCurrIconRow.cancelFadeAnimator();
3660 }
3661 mHandler.removeCallbacks(mCheckForDrag);
Mady Mellor4b80b102016-01-22 08:03:58 -08003662 }
3663
3664 private final class CheckForDrag implements Runnable {
3665 @Override
3666 public void run() {
3667 final float translation = getTranslation(mTranslatingParentView);
3668 final float absTransX = Math.abs(translation);
3669 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
3670 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003671 if ((mCurrIconRow != null && (!mCurrIconRow.isVisible()
3672 || mCurrIconRow.isIconLocationChange(translation)))
3673 && absTransX >= bounceBackToGearWidth * 0.4
Mady Mellor4b80b102016-01-22 08:03:58 -08003674 && absTransX < notiThreshold) {
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003675 // Fade in the gear
Mady Mellor4b80b102016-01-22 08:03:58 -08003676 mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
3677 notiThreshold);
Mady Mellor4b80b102016-01-22 08:03:58 -08003678 }
3679 }
3680 }
3681
3682 private void resetExposedGearView() {
3683 if (mGearExposedView == null || mGearExposedView == mTranslatingParentView) {
3684 // If no gear is showing or it's showing for this view we do nothing.
3685 return;
3686 }
3687
3688 final View prevGearExposedView = mGearExposedView;
3689 mGearExposedView = null;
Mady Mellor3a5e8dd2016-03-12 00:13:23 +00003690 mGearSnappedTo = false;
Mady Mellor34958fa2016-02-23 09:52:17 -08003691 Animator anim = getViewTranslationAnimator(prevGearExposedView,
3692 0 /* leftTarget */, null /* updateListener */);
3693 if (anim != null) {
3694 anim.start();
3695 }
Mady Mellor4b80b102016-01-22 08:03:58 -08003696 }
3697 }
3698
Selim Cinek33223572016-02-19 19:32:22 -08003699 private void updateContinuousShadowDrawing() {
3700 boolean continuousShadowUpdate = mAnimationRunning
3701 || !mAmbientState.getDraggedViews().isEmpty();
3702 if (continuousShadowUpdate != mContinuousShadowUpdate) {
3703 if (continuousShadowUpdate) {
3704 getViewTreeObserver().addOnPreDrawListener(mShadowUpdater);
3705 } else {
3706 getViewTreeObserver().removeOnPreDrawListener(mShadowUpdater);
3707 }
3708 }
3709 }
3710
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003711 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02003712
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003713 static AnimationFilter[] FILTERS = new AnimationFilter[] {
3714
3715 // ANIMATION_TYPE_ADD
3716 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003717 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003718 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003719 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003720 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003721 .animateZ()
3722 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003723
3724 // ANIMATION_TYPE_REMOVE
3725 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003726 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003727 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003728 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003729 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003730 .animateZ()
3731 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003732
3733 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3734 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003735 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003736 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003737 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003738 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003739 .animateZ()
3740 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003741
3742 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3743 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003744 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003745 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003746 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003747 .animateY()
3748 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003749 .animateZ(),
3750
3751 // ANIMATION_TYPE_START_DRAG
3752 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003753 .animateShadowAlpha(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003754
3755 // ANIMATION_TYPE_SNAP_BACK
3756 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003757 .animateShadowAlpha()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003758 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003759
3760 // ANIMATION_TYPE_ACTIVATED_CHILD
3761 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003762 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003763
3764 // ANIMATION_TYPE_DIMMED
3765 new AnimationFilter()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003766 .animateDimmed(),
3767
3768 // ANIMATION_TYPE_CHANGE_POSITION
3769 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003770 .animateAlpha() // maybe the children change positions
3771 .animateShadowAlpha()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003772 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003773 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003774 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04003775 .animateZ(),
3776
3777 // ANIMATION_TYPE_DARK
3778 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01003779 .animateDark()
3780 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003781
3782 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3783 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003784 .animateShadowAlpha()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003785 .animateHeight()
3786 .animateTopInset()
3787 .animateY()
3788 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02003789 .animateZ()
3790 .hasDelays(),
3791
3792 // ANIMATION_TYPE_HIDE_SENSITIVE
3793 new AnimationFilter()
3794 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02003795
3796 // ANIMATION_TYPE_VIEW_RESIZE
3797 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003798 .animateShadowAlpha()
Selim Cineka5e211b2014-08-11 17:35:48 +02003799 .animateHeight()
3800 .animateTopInset()
3801 .animateY()
3802 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02003803
Selim Cinekb5605e52015-02-20 18:21:41 +01003804 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
3805 new AnimationFilter()
3806 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003807 .animateShadowAlpha()
Selim Cinekb5605e52015-02-20 18:21:41 +01003808 .animateHeight()
3809 .animateTopInset()
3810 .animateY()
3811 .animateZ(),
3812
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003813 // ANIMATION_TYPE_HEADS_UP_APPEAR
3814 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003815 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003816 .animateHeight()
3817 .animateTopInset()
3818 .animateY()
3819 .animateZ(),
3820
3821 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3822 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003823 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003824 .animateHeight()
3825 .animateTopInset()
3826 .animateY()
3827 .animateZ(),
3828
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003829 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3830 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003831 .animateShadowAlpha()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003832 .animateHeight()
3833 .animateTopInset()
3834 .animateY()
3835 .animateZ()
3836 .hasDelays(),
3837
Selim Cineka59ecc32015-04-07 10:51:49 -07003838 // ANIMATION_TYPE_HEADS_UP_OTHER
3839 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003840 .animateShadowAlpha()
Selim Cineka59ecc32015-04-07 10:51:49 -07003841 .animateHeight()
3842 .animateTopInset()
3843 .animateY()
3844 .animateZ(),
3845
Selim Cinekd9acca52014-09-01 22:33:25 +02003846 // ANIMATION_TYPE_EVERYTHING
3847 new AnimationFilter()
3848 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003849 .animateShadowAlpha()
Selim Cinekd9acca52014-09-01 22:33:25 +02003850 .animateDark()
Selim Cinekd9acca52014-09-01 22:33:25 +02003851 .animateDimmed()
3852 .animateHideSensitive()
3853 .animateHeight()
3854 .animateTopInset()
3855 .animateY()
3856 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003857 };
3858
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003859 static int[] LENGTHS = new int[] {
3860
3861 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003862 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003863
3864 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003865 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003866
3867 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3868 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3869
3870 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3871 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3872
3873 // ANIMATION_TYPE_START_DRAG
3874 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3875
3876 // ANIMATION_TYPE_SNAP_BACK
3877 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3878
3879 // ANIMATION_TYPE_ACTIVATED_CHILD
3880 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
3881
3882 // ANIMATION_TYPE_DIMMED
3883 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003884
3885 // ANIMATION_TYPE_CHANGE_POSITION
3886 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04003887
3888 // ANIMATION_TYPE_DARK
3889 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003890
3891 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3892 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02003893
3894 // ANIMATION_TYPE_HIDE_SENSITIVE
3895 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02003896
3897 // ANIMATION_TYPE_VIEW_RESIZE
3898 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02003899
Selim Cinekb5605e52015-02-20 18:21:41 +01003900 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08003901 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01003902
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003903 // ANIMATION_TYPE_HEADS_UP_APPEAR
3904 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
3905
3906 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3907 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3908
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003909 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3910 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3911
Selim Cineka59ecc32015-04-07 10:51:49 -07003912 // ANIMATION_TYPE_HEADS_UP_OTHER
3913 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3914
Selim Cinekd9acca52014-09-01 22:33:25 +02003915 // ANIMATION_TYPE_EVERYTHING
3916 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003917 };
3918
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003919 static final int ANIMATION_TYPE_ADD = 0;
3920 static final int ANIMATION_TYPE_REMOVE = 1;
3921 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
3922 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
3923 static final int ANIMATION_TYPE_START_DRAG = 4;
3924 static final int ANIMATION_TYPE_SNAP_BACK = 5;
3925 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
3926 static final int ANIMATION_TYPE_DIMMED = 7;
3927 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04003928 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003929 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02003930 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02003931 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01003932 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003933 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
3934 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003935 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
3936 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
3937 static final int ANIMATION_TYPE_EVERYTHING = 18;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003938
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003939 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
3940 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
3941
Selim Cinek572bbd42014-04-25 16:43:27 +02003942 final long eventStartTime;
3943 final View changingView;
3944 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003945 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003946 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003947 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003948 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07003949 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02003950
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003951 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003952 this(view, type, LENGTHS[type]);
3953 }
3954
3955 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003956 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
3957 changingView = view;
3958 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003959 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003960 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003961 }
3962
3963 /**
3964 * Combines the length of several animation events into a single value.
3965 *
3966 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003967 * @return The combined length. Depending on the event types, this might be the maximum of
3968 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003969 */
3970 static long combineLength(ArrayList<AnimationEvent> events) {
3971 long length = 0;
3972 int size = events.size();
3973 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003974 AnimationEvent event = events.get(i);
3975 length = Math.max(length, event.length);
3976 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
3977 return event.length;
3978 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003979 }
3980 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02003981 }
3982 }
3983
Selim Cinek67b22602014-03-10 15:40:16 +01003984}