blob: f078b53a9e10fcbcbd05ea0ce4585e13925d7829 [file] [log] [blame]
Selim Cinek67b22602014-03-10 15:40:16 +01001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.systemui.statusbar.stack;
18
Selim Cinek614576e2016-01-20 10:54:09 -080019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Mady Mellor4b80b102016-01-22 08:03:58 -080021import android.animation.AnimatorSet;
Selim Cinek614576e2016-01-20 10:54:09 -080022import android.animation.ObjectAnimator;
23import android.animation.PropertyValuesHolder;
Selim Cinekd35c2792016-01-21 13:20:57 -080024import android.animation.TimeAnimator;
25import android.animation.ValueAnimator;
Mady Mellor4b80b102016-01-22 08:03:58 -080026import android.animation.ValueAnimator.AnimatorUpdateListener;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010027import android.annotation.Nullable;
Selim Cinek67b22602014-03-10 15:40:16 +010028import android.content.Context;
29import android.content.res.Configuration;
Selim Cinek67b22602014-03-10 15:40:16 +010030import android.graphics.Canvas;
Selim Cinekd35c2792016-01-21 13:20:57 -080031import android.graphics.Color;
Selim Cinek67b22602014-03-10 15:40:16 +010032import android.graphics.Paint;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +010033import android.graphics.PointF;
Selim Cinek6811d722016-01-19 17:53:12 -080034import android.graphics.PorterDuff;
35import android.graphics.PorterDuffXfermode;
36import android.graphics.Rect;
Mady Mellor4b80b102016-01-22 08:03:58 -080037import android.os.Handler;
Selim Cinek67b22602014-03-10 15:40:16 +010038import android.util.AttributeSet;
39import android.util.Log;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070040import android.util.Pair;
Selim Cinek67b22602014-03-10 15:40:16 +010041import android.view.MotionEvent;
42import android.view.VelocityTracker;
43import android.view.View;
44import android.view.ViewConfiguration;
45import android.view.ViewGroup;
Selim Cinek343e6e22014-04-11 21:23:30 +020046import android.view.ViewTreeObserver;
Selim Cinek572bbd42014-04-25 16:43:27 +020047import android.view.animation.AnimationUtils;
Selim Cinek614576e2016-01-20 10:54:09 -080048import android.view.animation.Interpolator;
Selim Cinek67b22602014-03-10 15:40:16 +010049import android.widget.OverScroller;
Jorim Jaggi56306252014-07-03 00:40:09 +020050
Selim Cinek67b22602014-03-10 15:40:16 +010051import com.android.systemui.ExpandHelper;
Winsonc0d70582016-01-29 10:24:39 -080052import com.android.systemui.Interpolators;
Selim Cinek67b22602014-03-10 15:40:16 +010053import com.android.systemui.R;
54import com.android.systemui.SwipeHelper;
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070055import com.android.systemui.classifier.FalsingManager;
Selim Cineka32ab602014-06-11 15:06:01 +020056import com.android.systemui.statusbar.ActivatableNotificationView;
Dan Sandlereceda3d2014-07-21 15:35:01 -040057import com.android.systemui.statusbar.DismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +020058import com.android.systemui.statusbar.EmptyShadeView;
Selim Cinek67b22602014-03-10 15:40:16 +010059import com.android.systemui.statusbar.ExpandableNotificationRow;
Jorim Jaggibe565df2014-04-28 17:51:23 +020060import com.android.systemui.statusbar.ExpandableView;
Selim Cinek2cd45df2015-06-09 18:00:07 -070061import com.android.systemui.statusbar.NotificationOverflowContainer;
Mady Mellor4b80b102016-01-22 08:03:58 -080062import com.android.systemui.statusbar.NotificationSettingsIconRow;
63import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +010064import com.android.systemui.statusbar.StackScrollerDecorView;
Selim Cinekcb2b6732014-09-05 16:17:22 +020065import com.android.systemui.statusbar.StatusBarState;
Selim Cinekb5605e52015-02-20 18:21:41 +010066import com.android.systemui.statusbar.phone.NotificationGroupManager;
Selim Cinek19c8c702014-08-25 22:09:19 +020067import com.android.systemui.statusbar.phone.PhoneStatusBar;
Selim Cinekaac93252015-04-14 20:04:12 -070068import com.android.systemui.statusbar.phone.ScrimController;
Selim Cinekb8f09cf2015-03-16 17:09:28 -070069import com.android.systemui.statusbar.policy.HeadsUpManager;
Selim Cinekb6d85eb2014-03-28 20:21:01 +010070import com.android.systemui.statusbar.policy.ScrollAdapter;
Selim Cinek67b22602014-03-10 15:40:16 +010071
Selim Cinek572bbd42014-04-25 16:43:27 +020072import java.util.ArrayList;
Jorim Jaggiff9c9c42014-08-01 05:36:22 +020073import java.util.HashSet;
Selim Cinek572bbd42014-04-25 16:43:27 +020074
Selim Cinek67b22602014-03-10 15:40:16 +010075/**
76 * A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
77 */
78public class NotificationStackScrollLayout extends ViewGroup
Jorim Jaggibe565df2014-04-28 17:51:23 +020079 implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
Mady Mellor4b80b102016-01-22 08:03:58 -080080 ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
81 SettingsIconRowListener {
Selim Cinek67b22602014-03-10 15:40:16 +010082
Selim Cinekd35c2792016-01-21 13:20:57 -080083 public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
Selim Cinek3776fe02016-02-04 13:32:43 -080084 private static final String TAG = "StackScroller";
Selim Cinek67b22602014-03-10 15:40:16 +010085 private static final boolean DEBUG = false;
Selim Cinek1408eb52014-06-02 14:45:38 +020086 private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
87 private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
Jorim Jaggi47c85a32014-06-05 17:25:40 +020088 private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
Selim Cinek67b22602014-03-10 15:40:16 +010089
90 /**
91 * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
92 */
93 private static final int INVALID_POINTER = -1;
94
Selim Cinek1408eb52014-06-02 14:45:38 +020095 private ExpandHelper mExpandHelper;
Selim Cinek67b22602014-03-10 15:40:16 +010096 private SwipeHelper mSwipeHelper;
Selim Cinek343e6e22014-04-11 21:23:30 +020097 private boolean mSwipingInProgress;
Selim Cinek67b22602014-03-10 15:40:16 +010098 private int mCurrentStackHeight = Integer.MAX_VALUE;
Selim Cinekd35c2792016-01-21 13:20:57 -080099 private final Paint mBackgroundPaint = new Paint();
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100100
101 /**
102 * mCurrentStackHeight is the actual stack height, mLastSetStackHeight is the stack height set
103 * externally from {@link #setStackHeight}
104 */
105 private float mLastSetStackHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100106 private int mOwnScrollY;
107 private int mMaxLayoutHeight;
108
109 private VelocityTracker mVelocityTracker;
110 private OverScroller mScroller;
111 private int mTouchSlop;
112 private int mMinimumVelocity;
113 private int mMaximumVelocity;
Selim Cinek67b22602014-03-10 15:40:16 +0100114 private int mOverflingDistance;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200115 private float mMaxOverScroll;
Selim Cinek67b22602014-03-10 15:40:16 +0100116 private boolean mIsBeingDragged;
117 private int mLastMotionY;
Selim Cinek1408eb52014-06-02 14:45:38 +0200118 private int mDownX;
Selim Cinek67b22602014-03-10 15:40:16 +0100119 private int mActivePointerId;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100120 private boolean mTouchIsClick;
121 private float mInitialTouchX;
122 private float mInitialTouchY;
Selim Cinek67b22602014-03-10 15:40:16 +0100123
Selim Cinek67b22602014-03-10 15:40:16 +0100124 private Paint mDebugPaint;
Selim Cinek67b22602014-03-10 15:40:16 +0100125 private int mContentHeight;
126 private int mCollapsedSize;
Selim Cineka5eaa602014-05-12 21:27:47 +0200127 private int mBottomStackSlowDownHeight;
Selim Cinek67b22602014-03-10 15:40:16 +0100128 private int mBottomStackPeekSize;
Selim Cinek67b22602014-03-10 15:40:16 +0100129 private int mPaddingBetweenElements;
Selim Cinek61633a82016-01-25 15:54:10 -0800130 private int mIncreasedPaddingBetweenElements;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200131 private int mTopPadding;
Selim Cinekd83771e2014-07-04 16:45:31 +0200132 private int mCollapseSecondCardPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100133
134 /**
135 * The algorithm which calculates the properties for our children
136 */
Selim Cinekaf0dc312015-12-15 17:01:44 -0800137 private final StackScrollAlgorithm mStackScrollAlgorithm;
Selim Cinek67b22602014-03-10 15:40:16 +0100138
139 /**
140 * The current State this Layout is in
141 */
Selim Cinek572bbd42014-04-25 16:43:27 +0200142 private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200143 private AmbientState mAmbientState = new AmbientState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100144 private NotificationGroupManager mGroupManager;
Selim Cinek3776fe02016-02-04 13:32:43 -0800145 private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700146 private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
147 private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
148 private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
149 private ArrayList<View> mDragAnimPendingChildren = new ArrayList<>();
150 private ArrayList<View> mChildrenChangingPositions = new ArrayList<>();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +0200151 private HashSet<View> mFromMoreCardAdditions = new HashSet<>();
Selim Cineka59ecc32015-04-07 10:51:49 -0700152 private ArrayList<AnimationEvent> mAnimationEvents = new ArrayList<>();
153 private ArrayList<View> mSwipedOutViews = new ArrayList<>();
Selim Cinek572bbd42014-04-25 16:43:27 +0200154 private final StackStateAnimator mStateAnimator = new StackStateAnimator(this);
Jorim Jaggi75c95042014-05-16 19:09:59 +0200155 private boolean mAnimationsEnabled;
Selim Cinek159ffdb2014-06-04 22:24:18 +0200156 private boolean mChangePositionInProgress;
Selim Cinekef5127e2015-12-21 16:55:58 -0800157 private boolean mChildTransferInProgress;
Selim Cinek1685e632014-04-08 02:27:49 +0200158
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200159 /**
160 * The raw amount of the overScroll on the top, which is not rubber-banded.
161 */
162 private float mOverScrolledTopPixels;
163
164 /**
165 * The raw amount of the overScroll on the bottom, which is not rubber-banded.
166 */
167 private float mOverScrolledBottomPixels;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200168 private OnChildLocationsChangedListener mListener;
Jorim Jaggi290600a2014-05-30 17:02:20 +0200169 private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200170 private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100171 private OnEmptySpaceClickListener mOnEmptySpaceClickListener;
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200172 private boolean mNeedsAnimation;
173 private boolean mTopPaddingNeedsAnimation;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200174 private boolean mDimmedNeedsAnimation;
Jorim Jaggiae441282014-08-01 02:45:18 +0200175 private boolean mHideSensitiveNeedsAnimation;
John Spurlockbf370992014-06-17 13:58:31 -0400176 private boolean mDarkNeedsAnimation;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100177 private int mDarkAnimationOriginIndex;
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200178 private boolean mActivateNeedsAnimation;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200179 private boolean mGoToFullShadeNeedsAnimation;
Selim Cinek572bbd42014-04-25 16:43:27 +0200180 private boolean mIsExpanded = true;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200181 private boolean mChildrenUpdateRequested;
Selim Cinekc27437b2014-05-14 10:23:33 +0200182 private boolean mIsExpansionChanging;
Jorim Jaggie4b840d2015-06-30 16:19:17 -0700183 private boolean mPanelTracking;
Selim Cinek1408eb52014-06-02 14:45:38 +0200184 private boolean mExpandingNotification;
185 private boolean mExpandedInThisMotion;
186 private boolean mScrollingEnabled;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400187 private DismissView mDismissView;
Jorim Jaggia2052ea2014-08-05 16:22:30 +0200188 private EmptyShadeView mEmptyShadeView;
Dan Sandlereceda3d2014-07-21 15:35:01 -0400189 private boolean mDismissAllInProgress;
Selim Cinek1408eb52014-06-02 14:45:38 +0200190
191 /**
192 * Was the scroller scrolled to the top when the down motion was observed?
193 */
194 private boolean mScrolledToTopOnFirstDown;
Selim Cinek1408eb52014-06-02 14:45:38 +0200195 /**
196 * The minimal amount of over scroll which is needed in order to switch to the quick settings
197 * when over scrolling on a expanded card.
198 */
199 private float mMinTopOverScrollToEscape;
200 private int mIntrinsicPadding;
Selim Cinekd2281152015-04-10 14:37:46 -0700201 private float mStackTranslation;
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200202 private float mTopPaddingOverflow;
Selim Cinek1408eb52014-06-02 14:45:38 +0200203 private boolean mDontReportNextOverScroll;
Selim Cineka5e211b2014-08-11 17:35:48 +0200204 private boolean mRequestViewResizeAnimationOnLayout;
205 private boolean mNeedViewResizeAnimation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100206 private View mExpandedGroupView;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700207 private boolean mEverythingNeedsAnimation;
Selim Cineka59ecc32015-04-07 10:51:49 -0700208
Selim Cinek1408eb52014-06-02 14:45:38 +0200209 /**
210 * The maximum scrollPosition which we are allowed to reach when a notification was expanded.
211 * This is needed to avoid scrolling too far after the notification was collapsed in the same
212 * motion.
213 */
214 private int mMaxScrollAfterExpand;
Dan Sandler4247a5c2014-07-23 15:58:08 -0400215 private SwipeHelper.LongPressListener mLongPressListener;
Mady Mellor4b80b102016-01-22 08:03:58 -0800216 private GearDisplayedListener mGearDisplayedListener;
217
218 private NotificationSettingsIconRow mCurrIconRow;
219 private View mTranslatingParentView;
220 private View mGearExposedView;
Selim Cinek1408eb52014-06-02 14:45:38 +0200221
222 /**
223 * Should in this touch motion only be scrolling allowed? It's true when the scroller was
224 * animating.
225 */
226 private boolean mOnlyScrollingInThisMotion;
Jorim Jaggi56306252014-07-03 00:40:09 +0200227 private boolean mInterceptDelegateEnabled;
228 private boolean mDelegateToScrollView;
Selim Cineka59ecc32015-04-07 10:51:49 -0700229 private boolean mDisallowScrollingInThisMotion;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700230 private long mGoToFullShadeDelay;
Selim Cinek1f553cf2014-05-02 12:01:36 +0200231 private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
Selim Cinek572bbd42014-04-25 16:43:27 +0200232 = new ViewTreeObserver.OnPreDrawListener() {
233 @Override
234 public boolean onPreDraw() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200235 updateChildren();
236 mChildrenUpdateRequested = false;
237 getViewTreeObserver().removeOnPreDrawListener(this);
Selim Cinek572bbd42014-04-25 16:43:27 +0200238 return true;
239 }
240 };
Selim Cinek19c8c702014-08-25 22:09:19 +0200241 private PhoneStatusBar mPhoneStatusBar;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100242 private int[] mTempInt2 = new int[2];
Selim Cinekb5605e52015-02-20 18:21:41 +0100243 private boolean mGenerateChildOrderChangedEvent;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700244 private HashSet<Runnable> mAnimationFinishedRunnables = new HashSet<>();
Selim Cinek0fccc722015-07-29 17:04:36 -0700245 private HashSet<View> mClearOverlayViewsWhenFinished = new HashSet<>();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700246 private HashSet<Pair<ExpandableNotificationRow, Boolean>> mHeadsUpChangeAnimations
247 = new HashSet<>();
248 private HeadsUpManager mHeadsUpManager;
249 private boolean mTrackingHeadsUp;
Selim Cinekaac93252015-04-14 20:04:12 -0700250 private ScrimController mScrimController;
Selim Cinekbbc580b2015-06-03 14:11:03 +0200251 private boolean mForceNoOverlappingRendering;
Selim Cinek2cd45df2015-06-09 18:00:07 -0700252 private NotificationOverflowContainer mOverflowContainer;
Selim Cineke0890e52015-06-17 11:17:08 -0700253 private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700254 private FalsingManager mFalsingManager;
Selim Cinek6811d722016-01-19 17:53:12 -0800255 private boolean mAnimationRunning;
256 private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
257 = new ViewTreeObserver.OnPreDrawListener() {
258 @Override
259 public boolean onPreDraw() {
Selim Cinek614576e2016-01-20 10:54:09 -0800260 // if it needs animation
261 if (!mNeedsAnimation && !mChildrenUpdateRequested) {
262 updateBackground();
263 }
Selim Cinek6811d722016-01-19 17:53:12 -0800264 return true;
265 }
266 };
267 private Rect mBackgroundBounds = new Rect();
Selim Cinek614576e2016-01-20 10:54:09 -0800268 private Rect mStartAnimationRect = new Rect();
269 private Rect mEndAnimationRect = new Rect();
Selim Cinekd35c2792016-01-21 13:20:57 -0800270 private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
Selim Cinek614576e2016-01-20 10:54:09 -0800271 private boolean mAnimateNextBackgroundBottom;
272 private boolean mAnimateNextBackgroundTop;
273 private ObjectAnimator mBottomAnimator = null;
274 private ObjectAnimator mTopAnimator = null;
275 private ActivatableNotificationView mFirstVisibleBackgroundChild = null;
276 private ActivatableNotificationView mLastVisibleBackgroundChild = null;
Selim Cinekd35c2792016-01-21 13:20:57 -0800277 private int mBgColor;
278 private float mDimAmount;
279 private ValueAnimator mDimAnimator;
280 private Animator.AnimatorListener mDimEndListener = new AnimatorListenerAdapter() {
281 @Override
282 public void onAnimationEnd(Animator animation) {
283 mDimAnimator = null;
284 }
285 };
286 private ValueAnimator.AnimatorUpdateListener mDimUpdateListener
287 = new ValueAnimator.AnimatorUpdateListener() {
288
289 @Override
290 public void onAnimationUpdate(ValueAnimator animation) {
291 setDimAmount((Float) animation.getAnimatedValue());
292 }
293 };
Jason Monk16ac3772016-02-10 15:39:21 -0500294 private ViewGroup mQsContainer;
Selim Cinek67b22602014-03-10 15:40:16 +0100295
296 public NotificationStackScrollLayout(Context context) {
297 this(context, null);
298 }
299
300 public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
301 this(context, attrs, 0);
302 }
303
304 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr) {
305 this(context, attrs, defStyleAttr, 0);
306 }
307
308 public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
309 int defStyleRes) {
310 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinekd35c2792016-01-21 13:20:57 -0800311 mBgColor = context.getColor(R.color.notification_shade_background_color);
Selim Cinek1cf41c12014-08-12 20:06:19 +0200312 int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
313 int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
314 mExpandHelper = new ExpandHelper(getContext(), this,
315 minHeight, maxHeight);
316 mExpandHelper.setEventSource(this);
317 mExpandHelper.setScrollAdapter(this);
Mady Mellor4b80b102016-01-22 08:03:58 -0800318 mSwipeHelper = new NotificationSwipeHelper(SwipeHelper.X, this, getContext());
Selim Cinek1cf41c12014-08-12 20:06:19 +0200319 mSwipeHelper.setLongPressListener(mLongPressListener);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800320 mStackScrollAlgorithm = new StackScrollAlgorithm(context);
Selim Cinek67b22602014-03-10 15:40:16 +0100321 initView(context);
Selim Cinek6811d722016-01-19 17:53:12 -0800322 setWillNotDraw(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100323 if (DEBUG) {
Selim Cinek67b22602014-03-10 15:40:16 +0100324 mDebugPaint = new Paint();
325 mDebugPaint.setColor(0xffff0000);
326 mDebugPaint.setStrokeWidth(2);
327 mDebugPaint.setStyle(Paint.Style.STROKE);
328 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700329 mFalsingManager = FalsingManager.getInstance(context);
Selim Cinekd35c2792016-01-21 13:20:57 -0800330 mBackgroundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
Selim Cinek67b22602014-03-10 15:40:16 +0100331 }
332
333 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800334 public void onGearTouched(ExpandableNotificationRow row) {
335 if (mLongPressListener != null) {
336 mLongPressListener.onLongPress(row, 0, 0);
337 }
338 }
339
340 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100341 protected void onDraw(Canvas canvas) {
Selim Cinekd35c2792016-01-21 13:20:57 -0800342 canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom, mBackgroundPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100343 if (DEBUG) {
Selim Cinek816c8e42015-11-19 12:00:45 -0800344 int y = mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +0100345 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200346 y = (int) (getLayoutHeight() - mBottomStackPeekSize
Selim Cineka5eaa602014-05-12 21:27:47 +0200347 - mBottomStackSlowDownHeight);
Selim Cinek34c0a8d2014-05-12 00:01:43 +0200348 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
349 y = (int) (getLayoutHeight() - mBottomStackPeekSize);
Selim Cinek67b22602014-03-10 15:40:16 +0100350 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
351 y = (int) getLayoutHeight();
352 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Jorim Jaggi1d480692014-05-20 19:41:58 +0200353 y = getHeight() - getEmptyBottomMargin();
354 canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
Selim Cinek67b22602014-03-10 15:40:16 +0100355 }
356 }
357
Selim Cinekd35c2792016-01-21 13:20:57 -0800358 private void updateBackgroundDimming() {
359 float alpha = BACKGROUND_ALPHA_DIMMED + (1 - BACKGROUND_ALPHA_DIMMED) * (1.0f - mDimAmount);
360 // We need to manually blend in the background color
361 int scrimColor = mScrimController.getScrimBehindColor();
362 // SRC_OVER blending Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc
363 float alphaInv = 1 - alpha;
364 int color = Color.argb((int) (alpha * 255 + alphaInv * Color.alpha(scrimColor)),
365 (int) (Color.red(mBgColor) + alphaInv * Color.red(scrimColor)),
366 (int) (Color.green(mBgColor) + alphaInv * Color.green(scrimColor)),
367 (int) (Color.blue(mBgColor) + alphaInv * Color.blue(scrimColor)));
368 mBackgroundPaint.setColor(color);
369 invalidate();
370 }
371
Selim Cinek67b22602014-03-10 15:40:16 +0100372 private void initView(Context context) {
373 mScroller = new OverScroller(getContext());
374 setFocusable(true);
375 setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200376 setClipChildren(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100377 final ViewConfiguration configuration = ViewConfiguration.get(context);
378 mTouchSlop = configuration.getScaledTouchSlop();
379 mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
380 mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
Selim Cinek67b22602014-03-10 15:40:16 +0100381 mOverflingDistance = configuration.getScaledOverflingDistance();
Selim Cinek67b22602014-03-10 15:40:16 +0100382 mCollapsedSize = context.getResources()
Jorim Jaggife40f7d2014-04-28 15:20:04 +0200383 .getDimensionPixelSize(R.dimen.notification_min_height);
Selim Cinek67b22602014-03-10 15:40:16 +0100384 mBottomStackPeekSize = context.getResources()
385 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
Selim Cinekaf0dc312015-12-15 17:01:44 -0800386 mStackScrollAlgorithm.initView(context);
Selim Cinek61633a82016-01-25 15:54:10 -0800387 mPaddingBetweenElements = Math.max(1, context.getResources()
Selim Cinekcacc6042016-01-21 16:16:41 -0800388 .getDimensionPixelSize(R.dimen.notification_divider_height));
Selim Cinek61633a82016-01-25 15:54:10 -0800389 mIncreasedPaddingBetweenElements = context.getResources()
390 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
391
Selim Cinek587cbf32016-01-19 11:36:18 -0800392 mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
Selim Cinek1408eb52014-06-02 14:45:38 +0200393 mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
394 R.dimen.min_top_overscroll_to_qs);
Selim Cinekd83771e2014-07-04 16:45:31 +0200395 mCollapseSecondCardPadding = getResources().getDimensionPixelSize(
396 R.dimen.notification_collapse_second_card_padding);
Selim Cineka5eaa602014-05-12 21:27:47 +0200397 }
398
Selim Cinekaef92ef2014-06-06 18:06:04 +0200399 private void notifyHeightChangeListener(ExpandableView view) {
400 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100401 mOnHeightChangedListener.onHeightChanged(view, false /* needsAnimation */);
Selim Cinekaef92ef2014-06-06 18:06:04 +0200402 }
Selim Cinek67b22602014-03-10 15:40:16 +0100403 }
404
405 @Override
406 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
407 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Selim Cinek924c6122016-01-15 18:24:05 -0800408 measureChildren(widthMeasureSpec, heightMeasureSpec);
Selim Cinek67b22602014-03-10 15:40:16 +0100409 }
410
411 @Override
412 protected void onLayout(boolean changed, int l, int t, int r, int b) {
413
414 // we layout all our children centered on the top
415 float centerX = getWidth() / 2.0f;
416 for (int i = 0; i < getChildCount(); i++) {
417 View child = getChildAt(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100418 if (child.getVisibility() == GONE) {
419 continue;
420 }
Selim Cinek67b22602014-03-10 15:40:16 +0100421 float width = child.getMeasuredWidth();
422 float height = child.getMeasuredHeight();
Selim Cinek67b22602014-03-10 15:40:16 +0100423 child.layout((int) (centerX - width / 2.0f),
424 0,
425 (int) (centerX + width / 2.0f),
426 (int) height);
Selim Cinek67b22602014-03-10 15:40:16 +0100427 }
Jorim Jaggi1d480692014-05-20 19:41:58 +0200428 setMaxLayoutHeight(getHeight());
Selim Cinek67b22602014-03-10 15:40:16 +0100429 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +0200430 clampScrollPosition();
Selim Cinekb5605e52015-02-20 18:21:41 +0100431 if (mRequestViewResizeAnimationOnLayout) {
Selim Cinek5bc852a2015-12-21 12:19:09 -0800432 requestAnimationOnViewResize(null);
Selim Cinekb5605e52015-02-20 18:21:41 +0100433 mRequestViewResizeAnimationOnLayout = false;
434 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200435 requestChildrenUpdate();
Selim Cinek614576e2016-01-20 10:54:09 -0800436 updateFirstAndLastBackgroundViews();
Selim Cinek67b22602014-03-10 15:40:16 +0100437 }
438
Selim Cinek5bc852a2015-12-21 12:19:09 -0800439 private void requestAnimationOnViewResize(ExpandableNotificationRow row) {
440 if (mAnimationsEnabled && (mIsExpanded || row != null && row.isPinned())) {
Selim Cineka5e211b2014-08-11 17:35:48 +0200441 mNeedViewResizeAnimation = true;
442 mNeedsAnimation = true;
443 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200444 }
445
Selim Cinekc27437b2014-05-14 10:23:33 +0200446 public void updateSpeedBumpIndex(int newIndex) {
Selim Cinekc27437b2014-05-14 10:23:33 +0200447 mAmbientState.setSpeedBumpIndex(newIndex);
448 }
449
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200450 public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
451 mListener = listener;
452 }
453
454 /**
455 * Returns the location the given child is currently rendered at.
456 *
457 * @param child the child to get the location for
Selim Cinekb036ca42015-02-20 15:56:28 +0100458 * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200459 */
460 public int getChildLocation(View child) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100461 StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200462 if (childViewState == null) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100463 return StackViewState.LOCATION_UNKNOWN;
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200464 }
Christoph Studer12cf9e52014-10-29 17:35:30 +0100465 if (childViewState.gone) {
Selim Cinekb036ca42015-02-20 15:56:28 +0100466 return StackViewState.LOCATION_GONE;
Christoph Studer12cf9e52014-10-29 17:35:30 +0100467 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +0200468 return childViewState.location;
469 }
470
Selim Cinek67b22602014-03-10 15:40:16 +0100471 private void setMaxLayoutHeight(int maxLayoutHeight) {
472 mMaxLayoutHeight = maxLayoutHeight;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200473 updateAlgorithmHeightAndPadding();
Selim Cinek67b22602014-03-10 15:40:16 +0100474 }
475
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200476 private void updateAlgorithmHeightAndPadding() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700477 mAmbientState.setLayoutHeight(getLayoutHeight());
478 mAmbientState.setTopPadding(mTopPadding);
Selim Cinek67b22602014-03-10 15:40:16 +0100479 }
480
481 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +0200482 * @return whether the height of the layout needs to be adapted, in order to ensure that the
483 * last child is not in the bottom stack.
484 */
485 private boolean needsHeightAdaption() {
Jorim Jaggi1d480692014-05-20 19:41:58 +0200486 return getNotGoneChildCount() > 1;
Selim Cinek4a1ac842014-05-01 15:51:58 +0200487 }
488
Selim Cinek4a1ac842014-05-01 15:51:58 +0200489 /**
Selim Cinek67b22602014-03-10 15:40:16 +0100490 * Updates the children views according to the stack scroll algorithm. Call this whenever
491 * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
492 */
493 private void updateChildren() {
Selim Cinek3776fe02016-02-04 13:32:43 -0800494 updateScrollStateForAddedChildren();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200495 mAmbientState.setScrollY(mOwnScrollY);
496 mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200497 if (!isCurrentlyAnimating() && !mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200498 applyCurrentState();
Selim Cinek67b22602014-03-10 15:40:16 +0100499 } else {
Selim Cinekf4c19962014-05-01 21:55:31 +0200500 startAnimationToState();
Selim Cinek67b22602014-03-10 15:40:16 +0100501 }
502 }
503
Selim Cinek3776fe02016-02-04 13:32:43 -0800504 private void updateScrollStateForAddedChildren() {
505 if (mChildrenToAddAnimated.isEmpty()) {
506 return;
507 }
508 for (int i = 0; i < getChildCount(); i++) {
509 ExpandableView child = (ExpandableView) getChildAt(i);
510 if (mChildrenToAddAnimated.contains(child)) {
511 int startingPosition = getPositionInLinearLayout(child);
512 int padding = child.needsIncreasedPadding()
513 ? mIncreasedPaddingBetweenElements :
514 mPaddingBetweenElements;
515 int childHeight = getIntrinsicHeight(child) + padding;
516 if (startingPosition < mOwnScrollY) {
517 // This child starts off screen, so let's keep it offscreen to keep the others visible
518
519 mOwnScrollY += childHeight;
520 }
521 }
522 }
523 clampScrollPosition();
524 }
525
Selim Cinek319bdc42014-05-01 23:01:58 +0200526 private void requestChildrenUpdate() {
Selim Cinek1f553cf2014-05-02 12:01:36 +0200527 if (!mChildrenUpdateRequested) {
528 getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
529 mChildrenUpdateRequested = true;
530 invalidate();
531 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200532 }
533
Selim Cinek67b22602014-03-10 15:40:16 +0100534 private boolean isCurrentlyAnimating() {
Selim Cinek572bbd42014-04-25 16:43:27 +0200535 return mStateAnimator.isRunning();
Selim Cinek67b22602014-03-10 15:40:16 +0100536 }
537
Selim Cinekf7a14c02014-07-07 14:01:46 +0200538 private void clampScrollPosition() {
Selim Cinek67b22602014-03-10 15:40:16 +0100539 int scrollRange = getScrollRange();
540 if (scrollRange < mOwnScrollY) {
541 mOwnScrollY = scrollRange;
542 }
543 }
544
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200545 public int getTopPadding() {
546 return mTopPadding;
547 }
548
Selim Cinek1408eb52014-06-02 14:45:38 +0200549 private void setTopPadding(int topPadding, boolean animate) {
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200550 if (mTopPadding != topPadding) {
551 mTopPadding = topPadding;
552 updateAlgorithmHeightAndPadding();
553 updateContentHeight();
Jorim Jaggi75c95042014-05-16 19:09:59 +0200554 if (animate && mAnimationsEnabled && mIsExpanded) {
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200555 mTopPaddingNeedsAnimation = true;
556 mNeedsAnimation = true;
557 }
Selim Cinek319bdc42014-05-01 23:01:58 +0200558 requestChildrenUpdate();
Selim Cinekaef92ef2014-06-06 18:06:04 +0200559 notifyHeightChangeListener(null);
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200560 }
561 }
562
563 /**
564 * Update the height of the stack to a new height.
565 *
566 * @param height the new height of the stack
567 */
568 public void setStackHeight(float height) {
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +0100569 mLastSetStackHeight = height;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200570 setIsExpanded(height > 0.0f);
571 int newStackHeight = (int) height;
Selim Cinekd83771e2014-07-04 16:45:31 +0200572 int minStackHeight = getMinStackHeight();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200573 int stackHeight;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700574 float paddingOffset;
Selim Cinek131c1e22015-05-11 19:04:49 -0700575 boolean trackingHeadsUp = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp();
Selim Cinek31aada42015-12-18 17:51:15 -0800576 int normalUnfoldPositionStart = trackingHeadsUp
577 ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700578 : minStackHeight;
Selim Cinek684a4422015-04-15 16:18:39 -0700579 if (newStackHeight - mTopPadding - mTopPaddingOverflow >= normalUnfoldPositionStart
Jorim Jaggi58bef332014-11-21 18:10:32 +0100580 || getNotGoneChildCount() == 0) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700581 paddingOffset = mTopPaddingOverflow;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200582 stackHeight = newStackHeight;
583 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800584 int translationY;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700585 if (!trackingHeadsUp) {
Selim Cinek31aada42015-12-18 17:51:15 -0800586 // We did not reach the position yet where we actually start growing,
587 // so we translate the stack upwards.
588 translationY = (newStackHeight - minStackHeight);
589 // A slight parallax effect is introduced in order for the stack to catch up with
590 // the top card.
591 float partiallyThere = (newStackHeight - mTopPadding - mTopPaddingOverflow)
592 / minStackHeight;
593 partiallyThere = Math.max(0, partiallyThere);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700594 translationY += (1 - partiallyThere) * (mBottomStackPeekSize +
595 mCollapseSecondCardPadding);
596 } else {
Selim Cinek31aada42015-12-18 17:51:15 -0800597 translationY = (int) (height - normalUnfoldPositionStart);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700598 }
599 paddingOffset = translationY - mTopPadding;
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200600 stackHeight = (int) (height - (translationY - mTopPadding));
601 }
602 if (stackHeight != mCurrentStackHeight) {
603 mCurrentStackHeight = stackHeight;
604 updateAlgorithmHeightAndPadding();
Selim Cinek319bdc42014-05-01 23:01:58 +0200605 requestChildrenUpdate();
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +0200606 }
Selim Cinekd2281152015-04-10 14:37:46 -0700607 setStackTranslation(paddingOffset);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700608 }
609
Selim Cinekd2281152015-04-10 14:37:46 -0700610 public float getStackTranslation() {
611 return mStackTranslation;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700612 }
613
Selim Cinekd2281152015-04-10 14:37:46 -0700614 private void setStackTranslation(float stackTranslation) {
615 if (stackTranslation != mStackTranslation) {
616 mStackTranslation = stackTranslation;
617 mAmbientState.setStackTranslation(stackTranslation);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700618 requestChildrenUpdate();
619 }
Selim Cinek67b22602014-03-10 15:40:16 +0100620 }
621
622 /**
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100623 * 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 +0100624 * layout but it can also be made smaller by setting {@link #mCurrentStackHeight}
625 *
626 * @return either the layout height or the externally defined height, whichever is smaller
627 */
Selim Cinek343e6e22014-04-11 21:23:30 +0200628 private int getLayoutHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +0100629 return Math.min(mMaxLayoutHeight, mCurrentStackHeight);
630 }
631
Selim Cinek816c8e42015-11-19 12:00:45 -0800632 public int getFirstItemMinHeight() {
633 final ExpandableView firstChild = getFirstChildNotGone();
634 return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100635 }
636
637 public int getBottomStackPeekSize() {
638 return mBottomStackPeekSize;
639 }
640
Jorim Jaggi5ad92c52014-07-28 21:07:32 +0200641 public int getCollapseSecondCardPadding() {
642 return mCollapseSecondCardPadding;
643 }
644
Dan Sandler4247a5c2014-07-23 15:58:08 -0400645 public void setLongPressListener(SwipeHelper.LongPressListener listener) {
Selim Cinek67b22602014-03-10 15:40:16 +0100646 mSwipeHelper.setLongPressListener(listener);
Dan Sandler4247a5c2014-07-23 15:58:08 -0400647 mLongPressListener = listener;
Selim Cinek67b22602014-03-10 15:40:16 +0100648 }
649
Mady Mellor4b80b102016-01-22 08:03:58 -0800650 public void setGearDisplayedListener(GearDisplayedListener listener) {
651 mGearDisplayedListener = listener;
652 }
653
Jason Monk16ac3772016-02-10 15:39:21 -0500654 public void setQsContainer(ViewGroup qsContainer) {
655 mQsContainer = qsContainer;
Jorim Jaggi56306252014-07-03 00:40:09 +0200656 }
657
Selim Cinek67b22602014-03-10 15:40:16 +0100658 public void onChildDismissed(View v) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400659 if (mDismissAllInProgress) {
660 return;
661 }
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100662 setSwipingInProgress(false);
Selim Cinekeb973562014-05-02 17:07:49 +0200663 if (mDragAnimPendingChildren.contains(v)) {
664 // We start the swipe and finish it in the same frame, we don't want any animation
665 // for the drag
666 mDragAnimPendingChildren.remove(v);
667 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200668 mSwipedOutViews.add(v);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200669 mAmbientState.onDragFinished(v);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700670 if (v instanceof ExpandableNotificationRow) {
671 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
672 if (row.isHeadsUp()) {
Selim Cinek684a4422015-04-15 16:18:39 -0700673 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700674 }
675 }
676 final View veto = v.findViewById(R.id.veto);
677 if (veto != null && veto.getVisibility() != View.GONE) {
678 veto.performClick();
679 }
680 if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
Blazej Magnowski72323322015-07-24 11:49:40 -0700681
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700682 mFalsingManager.onNotificationDismissed();
683 if (mFalsingManager.shouldEnforceBouncer()) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700684 mPhoneStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
685 false /* dismissShade */, true /* afterKeyguardGone */);
686 }
Selim Cinekeb973562014-05-02 17:07:49 +0200687 }
688
689 @Override
Mady Mellor4b80b102016-01-22 08:03:58 -0800690 public void onChildSnappedBack(View animView, float targetLeft) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200691 mAmbientState.onDragFinished(animView);
Selim Cinekeb973562014-05-02 17:07:49 +0200692 if (!mDragAnimPendingChildren.contains(animView)) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200693 if (mAnimationsEnabled) {
694 mSnappedBackChildren.add(animView);
695 mNeedsAnimation = true;
696 }
Selim Cinekeb973562014-05-02 17:07:49 +0200697 requestChildrenUpdate();
Selim Cinekeb973562014-05-02 17:07:49 +0200698 } else {
699 // We start the swipe and snap back in the same frame, we don't want any animation
700 mDragAnimPendingChildren.remove(animView);
701 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800702
703 if (targetLeft == 0 && mCurrIconRow != null) {
704 mCurrIconRow.resetState();
705 if (mGearExposedView != null && mGearExposedView == mTranslatingParentView) {
706 mGearExposedView = null;
707 }
708 }
Selim Cinek67b22602014-03-10 15:40:16 +0100709 }
710
Adrian Roos5d9cc662014-05-28 17:08:13 +0200711 @Override
712 public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
Selim Cinek131c1e22015-05-11 19:04:49 -0700713 if (!mIsExpanded && isPinnedHeadsUp(animView) && canChildBeDismissed(animView)) {
Selim Cinekaac93252015-04-14 20:04:12 -0700714 mScrimController.setTopHeadsUpDragAmount(animView,
715 Math.min(Math.abs(swipeProgress - 1.0f), 1.0f));
716 }
Mady Mellor4b80b102016-01-22 08:03:58 -0800717 return true; // Don't fade out the notification
Adrian Roos5d9cc662014-05-28 17:08:13 +0200718 }
719
Selim Cinek67b22602014-03-10 15:40:16 +0100720 public void onBeginDrag(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700721 mFalsingManager.onNotificatonStartDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100722 setSwipingInProgress(true);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200723 mAmbientState.onBeginDrag(v);
Selim Cinek131c1e22015-05-11 19:04:49 -0700724 if (mAnimationsEnabled && (mIsExpanded || !isPinnedHeadsUp(v))) {
Jorim Jaggi75c95042014-05-16 19:09:59 +0200725 mDragAnimPendingChildren.add(v);
726 mNeedsAnimation = true;
727 }
Selim Cinekeb973562014-05-02 17:07:49 +0200728 requestChildrenUpdate();
Selim Cinek67b22602014-03-10 15:40:16 +0100729 }
730
Selim Cinek684a4422015-04-15 16:18:39 -0700731 public static boolean isPinnedHeadsUp(View v) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700732 if (v instanceof ExpandableNotificationRow) {
733 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
Selim Cinek684a4422015-04-15 16:18:39 -0700734 return row.isHeadsUp() && row.isPinned();
Selim Cineka59ecc32015-04-07 10:51:49 -0700735 }
736 return false;
737 }
738
739 private boolean isHeadsUp(View v) {
740 if (v instanceof ExpandableNotificationRow) {
741 ExpandableNotificationRow row = (ExpandableNotificationRow) v;
742 return row.isHeadsUp();
743 }
744 return false;
745 }
746
Selim Cinek67b22602014-03-10 15:40:16 +0100747 public void onDragCancelled(View v) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700748 mFalsingManager.onNotificatonStopDismissing();
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100749 setSwipingInProgress(false);
Selim Cinek67b22602014-03-10 15:40:16 +0100750 }
751
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700752 @Override
753 public float getFalsingThresholdFactor() {
Jorim Jaggi50ff3af2015-08-12 18:35:42 -0700754 return mPhoneStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700755 }
756
Mady Mellor4b80b102016-01-22 08:03:58 -0800757 @Override
Selim Cinek67b22602014-03-10 15:40:16 +0100758 public View getChildAtPosition(MotionEvent ev) {
Mady Mellor4b80b102016-01-22 08:03:58 -0800759 View child = getChildAtPosition(ev.getX(), ev.getY());
760 if (child instanceof ExpandableNotificationRow) {
761 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
762 ExpandableNotificationRow parent = row.getNotificationParent();
763 if (mGearExposedView != null && parent != null
764 && parent.areChildrenExpanded() && mGearExposedView == parent) {
765 // In this case the group is expanded and showing the gear for the
766 // group, further interaction should apply to the group, not any
767 // child notifications so we use the parent of the child.
768 child = row.getNotificationParent();
769 }
770 }
771 return child;
Selim Cinek67b22602014-03-10 15:40:16 +0100772 }
773
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100774 public ExpandableView getClosestChildAtRawPosition(float touchX, float touchY) {
775 getLocationOnScreen(mTempInt2);
776 float localTouchY = touchY - mTempInt2[1];
777
778 ExpandableView closestChild = null;
779 float minDist = Float.MAX_VALUE;
780
781 // find the view closest to the location, accounting for GONE views
782 final int count = getChildCount();
783 for (int childIdx = 0; childIdx < count; childIdx++) {
784 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
785 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800786 || slidingChild instanceof StackScrollerDecorView) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100787 continue;
788 }
789 float childTop = slidingChild.getTranslationY();
790 float top = childTop + slidingChild.getClipTopAmount();
791 float bottom = childTop + slidingChild.getActualHeight();
792
793 float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
794 if (dist < minDist) {
795 closestChild = slidingChild;
796 minDist = dist;
797 }
798 }
799 return closestChild;
800 }
801
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200802 public ExpandableView getChildAtRawPosition(float touchX, float touchY) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +0100803 getLocationOnScreen(mTempInt2);
804 return getChildAtPosition(touchX - mTempInt2[0], touchY - mTempInt2[1]);
Selim Cinek67b22602014-03-10 15:40:16 +0100805 }
806
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200807 public ExpandableView getChildAtPosition(float touchX, float touchY) {
Selim Cinek67b22602014-03-10 15:40:16 +0100808 // find the view under the pointer, accounting for GONE views
809 final int count = getChildCount();
810 for (int childIdx = 0; childIdx < count; childIdx++) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200811 ExpandableView slidingChild = (ExpandableView) getChildAt(childIdx);
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100812 if (slidingChild.getVisibility() == GONE
Selim Cinek4fd5dfc2016-01-19 15:16:15 -0800813 || slidingChild instanceof StackScrollerDecorView) {
Selim Cinek67b22602014-03-10 15:40:16 +0100814 continue;
815 }
Selim Cinek89faff12014-06-19 16:29:04 -0700816 float childTop = slidingChild.getTranslationY();
817 float top = childTop + slidingChild.getClipTopAmount();
Selim Cinekabdc5a02014-09-02 13:46:00 +0200818 float bottom = childTop + slidingChild.getActualHeight();
Jorim Jaggi28f0e592014-08-05 22:03:07 +0200819
820 // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
821 // camera affordance).
822 int left = 0;
823 int right = getWidth();
Selim Cinek67b22602014-03-10 15:40:16 +0100824
825 if (touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100826 if (slidingChild instanceof ExpandableNotificationRow) {
827 ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
Selim Cinek131c1e22015-05-11 19:04:49 -0700828 if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
Selim Cinek5bc852a2015-12-21 12:19:09 -0800829 && mHeadsUpManager.getTopEntry().entry.row != row
830 && mGroupManager.getGroupSummary(
831 mHeadsUpManager.getTopEntry().entry.row.getStatusBarNotification())
832 != row) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700833 continue;
834 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100835 return row.getViewAtPosition(touchY - childTop);
836 }
Selim Cinek67b22602014-03-10 15:40:16 +0100837 return slidingChild;
838 }
839 }
840 return null;
841 }
842
843 public boolean canChildBeExpanded(View v) {
844 return v instanceof ExpandableNotificationRow
Selim Cinek8d490d42015-04-10 00:05:50 -0700845 && ((ExpandableNotificationRow) v).isExpandable()
846 && !((ExpandableNotificationRow) v).isHeadsUp();
Selim Cinek67b22602014-03-10 15:40:16 +0100847 }
848
849 public void setUserExpandedChild(View v, boolean userExpanded) {
850 if (v instanceof ExpandableNotificationRow) {
Selim Cinek388df6d2015-10-22 13:25:11 -0700851 ((ExpandableNotificationRow) v).setUserExpanded(userExpanded,
852 true /* allowChildrenExpansion */);
Selim Cinek67b22602014-03-10 15:40:16 +0100853 }
854 }
855
856 public void setUserLockedChild(View v, boolean userLocked) {
857 if (v instanceof ExpandableNotificationRow) {
858 ((ExpandableNotificationRow) v).setUserLocked(userLocked);
859 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200860 removeLongPressCallback();
861 requestDisallowInterceptTouchEvent(true);
862 }
863
864 @Override
865 public void expansionStateChanged(boolean isExpanding) {
866 mExpandingNotification = isExpanding;
867 if (!mExpandedInThisMotion) {
868 mMaxScrollAfterExpand = mOwnScrollY;
869 mExpandedInThisMotion = true;
870 }
871 }
872
873 public void setScrollingEnabled(boolean enable) {
874 mScrollingEnabled = enable;
875 }
876
877 public void setExpandingEnabled(boolean enable) {
878 mExpandHelper.setEnabled(enable);
879 }
880
881 private boolean isScrollingEnabled() {
882 return mScrollingEnabled;
Selim Cinek67b22602014-03-10 15:40:16 +0100883 }
884
Selim Cinek67b22602014-03-10 15:40:16 +0100885 public boolean canChildBeDismissed(View v) {
Selim Cinek9c17b772015-07-07 20:37:09 -0700886 return StackScrollAlgorithm.canChildBeDismissed(v);
Selim Cinek67b22602014-03-10 15:40:16 +0100887 }
888
Selim Cinek19c8c702014-08-25 22:09:19 +0200889 @Override
890 public boolean isAntiFalsingNeeded() {
Selim Cinekcb2b6732014-09-05 16:17:22 +0200891 return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
Selim Cinek19c8c702014-08-25 22:09:19 +0200892 }
893
Selim Cinekb6d85eb2014-03-28 20:21:01 +0100894 private void setSwipingInProgress(boolean isSwiped) {
895 mSwipingInProgress = isSwiped;
896 if(isSwiped) {
897 requestDisallowInterceptTouchEvent(true);
898 }
Selim Cinek67b22602014-03-10 15:40:16 +0100899 }
900
901 @Override
902 protected void onConfigurationChanged(Configuration newConfig) {
903 super.onConfigurationChanged(newConfig);
904 float densityScale = getResources().getDisplayMetrics().density;
905 mSwipeHelper.setDensityScale(densityScale);
906 float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
907 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
908 initView(getContext());
909 }
910
Dan Sandlereceda3d2014-07-21 15:35:01 -0400911 public void dismissViewAnimated(View child, Runnable endRunnable, int delay, long duration) {
Dan Sandlereceda3d2014-07-21 15:35:01 -0400912 mSwipeHelper.dismissChild(child, 0, endRunnable, delay, true, duration);
Selim Cinek67b22602014-03-10 15:40:16 +0100913 }
914
915 @Override
916 public boolean onTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200917 boolean isCancelOrUp = ev.getActionMasked() == MotionEvent.ACTION_CANCEL
918 || ev.getActionMasked()== MotionEvent.ACTION_UP;
Selim Cinek3a9c10a2014-10-28 14:21:10 +0100919 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +0200920 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +0200921 if (mIsExpanded && !mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200922 if (isCancelOrUp) {
923 mExpandHelper.onlyObserveMovements(false);
924 }
925 boolean wasExpandingBefore = mExpandingNotification;
926 expandWantsIt = mExpandHelper.onTouchEvent(ev);
Selim Cinekf7a14c02014-07-07 14:01:46 +0200927 if (mExpandedInThisMotion && !mExpandingNotification && wasExpandingBefore
928 && !mDisallowScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200929 dispatchDownEventToScroller(ev);
930 }
931 }
Selim Cinek67b22602014-03-10 15:40:16 +0100932 boolean scrollerWantsIt = false;
Selim Cinek684a4422015-04-15 16:18:39 -0700933 if (mIsExpanded && !mSwipingInProgress && !mExpandingNotification
934 && !mDisallowScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100935 scrollerWantsIt = onScrollTouch(ev);
936 }
937 boolean horizontalSwipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +0200938 if (!mIsBeingDragged
939 && !mExpandingNotification
940 && !mExpandedInThisMotion
941 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +0100942 horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
943 }
Selim Cinek1408eb52014-06-02 14:45:38 +0200944 return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
945 }
946
947 private void dispatchDownEventToScroller(MotionEvent ev) {
948 MotionEvent downEvent = MotionEvent.obtain(ev);
949 downEvent.setAction(MotionEvent.ACTION_DOWN);
950 onScrollTouch(downEvent);
951 downEvent.recycle();
Selim Cinek67b22602014-03-10 15:40:16 +0100952 }
953
954 private boolean onScrollTouch(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +0200955 if (!isScrollingEnabled()) {
956 return false;
957 }
Jason Monk16ac3772016-02-10 15:39:21 -0500958 if (ev.getY() < mQsContainer.getBottom()) {
959 return false;
960 }
Selim Cinek67b22602014-03-10 15:40:16 +0100961 initVelocityTrackerIfNotExists();
962 mVelocityTracker.addMovement(ev);
963
964 final int action = ev.getAction();
965
966 switch (action & MotionEvent.ACTION_MASK) {
967 case MotionEvent.ACTION_DOWN: {
Jorim Jaggife6bfa62014-05-07 23:23:18 +0200968 if (getChildCount() == 0 || !isInContentBounds(ev)) {
Selim Cinek67b22602014-03-10 15:40:16 +0100969 return false;
970 }
971 boolean isBeingDragged = !mScroller.isFinished();
972 setIsBeingDragged(isBeingDragged);
Selim Cinek67b22602014-03-10 15:40:16 +0100973
974 /*
975 * If being flinged and user touches, stop the fling. isFinished
976 * will be false if being flinged.
977 */
978 if (!mScroller.isFinished()) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200979 mScroller.forceFinished(true);
Selim Cinek67b22602014-03-10 15:40:16 +0100980 }
981
982 // Remember where the motion event started
983 mLastMotionY = (int) ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +0200984 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +0100985 mActivePointerId = ev.getPointerId(0);
986 break;
987 }
988 case MotionEvent.ACTION_MOVE:
989 final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
990 if (activePointerIndex == -1) {
991 Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
992 break;
993 }
994
995 final int y = (int) ev.getY(activePointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +0200996 final int x = (int) ev.getX(activePointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +0100997 int deltaY = mLastMotionY - y;
Selim Cinek1408eb52014-06-02 14:45:38 +0200998 final int xDiff = Math.abs(x - mDownX);
999 final int yDiff = Math.abs(deltaY);
1000 if (!mIsBeingDragged && yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01001001 setIsBeingDragged(true);
1002 if (deltaY > 0) {
1003 deltaY -= mTouchSlop;
1004 } else {
1005 deltaY += mTouchSlop;
1006 }
1007 }
1008 if (mIsBeingDragged) {
1009 // Scroll to follow the motion event
1010 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02001011 int range = getScrollRange();
1012 if (mExpandedInThisMotion) {
1013 range = Math.min(range, mMaxScrollAfterExpand);
1014 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001015
1016 float scrollAmount;
1017 if (deltaY < 0) {
1018 scrollAmount = overScrollDown(deltaY);
1019 } else {
1020 scrollAmount = overScrollUp(deltaY, range);
1021 }
Selim Cinek67b22602014-03-10 15:40:16 +01001022
1023 // Calling overScrollBy will call onOverScrolled, which
1024 // calls onScrollChanged if applicable.
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001025 if (scrollAmount != 0.0f) {
1026 // The scrolling motion could not be compensated with the
1027 // existing overScroll, we have to scroll the view
1028 overScrollBy(0, (int) scrollAmount, 0, mOwnScrollY,
1029 0, range, 0, getHeight() / 2, true);
Selim Cinek67b22602014-03-10 15:40:16 +01001030 }
Selim Cinek67b22602014-03-10 15:40:16 +01001031 }
1032 break;
1033 case MotionEvent.ACTION_UP:
1034 if (mIsBeingDragged) {
1035 final VelocityTracker velocityTracker = mVelocityTracker;
1036 velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
1037 int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
1038
Selim Cinek1408eb52014-06-02 14:45:38 +02001039 if (shouldOverScrollFling(initialVelocity)) {
1040 onOverScrollFling(true, initialVelocity);
1041 } else {
1042 if (getChildCount() > 0) {
1043 if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
1044 float currentOverScrollTop = getCurrentOverScrollAmount(true);
1045 if (currentOverScrollTop == 0.0f || initialVelocity > 0) {
1046 fling(-initialVelocity);
1047 } else {
1048 onOverScrollFling(false, initialVelocity);
1049 }
1050 } else {
1051 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0,
1052 getScrollRange())) {
1053 postInvalidateOnAnimation();
1054 }
Selim Cinek67b22602014-03-10 15:40:16 +01001055 }
1056 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001057 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001058
1059 mActivePointerId = INVALID_POINTER;
1060 endDrag();
Selim Cinek67b22602014-03-10 15:40:16 +01001061 }
Selim Cinek48e746c2014-06-16 16:01:03 -07001062
Selim Cinek67b22602014-03-10 15:40:16 +01001063 break;
1064 case MotionEvent.ACTION_CANCEL:
1065 if (mIsBeingDragged && getChildCount() > 0) {
1066 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
1067 postInvalidateOnAnimation();
1068 }
1069 mActivePointerId = INVALID_POINTER;
1070 endDrag();
1071 }
1072 break;
1073 case MotionEvent.ACTION_POINTER_DOWN: {
1074 final int index = ev.getActionIndex();
1075 mLastMotionY = (int) ev.getY(index);
Selim Cinek1408eb52014-06-02 14:45:38 +02001076 mDownX = (int) ev.getX(index);
Selim Cinek67b22602014-03-10 15:40:16 +01001077 mActivePointerId = ev.getPointerId(index);
1078 break;
1079 }
1080 case MotionEvent.ACTION_POINTER_UP:
1081 onSecondaryPointerUp(ev);
1082 mLastMotionY = (int) ev.getY(ev.findPointerIndex(mActivePointerId));
Selim Cinek1408eb52014-06-02 14:45:38 +02001083 mDownX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
Selim Cinek67b22602014-03-10 15:40:16 +01001084 break;
1085 }
1086 return true;
1087 }
1088
Selim Cinek1408eb52014-06-02 14:45:38 +02001089 private void onOverScrollFling(boolean open, int initialVelocity) {
1090 if (mOverscrollTopChangedListener != null) {
1091 mOverscrollTopChangedListener.flingTopOverscroll(initialVelocity, open);
1092 }
1093 mDontReportNextOverScroll = true;
1094 setOverScrollAmount(0.0f, true, false);
1095 }
1096
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001097 /**
1098 * Perform a scroll upwards and adapt the overscroll amounts accordingly
1099 *
1100 * @param deltaY The amount to scroll upwards, has to be positive.
1101 * @return The amount of scrolling to be performed by the scroller,
1102 * not handled by the overScroll amount.
1103 */
1104 private float overScrollUp(int deltaY, int range) {
1105 deltaY = Math.max(deltaY, 0);
1106 float currentTopAmount = getCurrentOverScrollAmount(true);
1107 float newTopAmount = currentTopAmount - deltaY;
1108 if (currentTopAmount > 0) {
1109 setOverScrollAmount(newTopAmount, true /* onTop */,
1110 false /* animate */);
1111 }
1112 // Top overScroll might not grab all scrolling motion,
1113 // we have to scroll as well.
1114 float scrollAmount = newTopAmount < 0 ? -newTopAmount : 0.0f;
1115 float newScrollY = mOwnScrollY + scrollAmount;
1116 if (newScrollY > range) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001117 if (!mExpandedInThisMotion) {
1118 float currentBottomPixels = getCurrentOverScrolledPixels(false);
1119 // We overScroll on the top
1120 setOverScrolledPixels(currentBottomPixels + newScrollY - range,
1121 false /* onTop */,
1122 false /* animate */);
1123 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001124 mOwnScrollY = range;
1125 scrollAmount = 0.0f;
1126 }
1127 return scrollAmount;
1128 }
1129
1130 /**
1131 * Perform a scroll downward and adapt the overscroll amounts accordingly
1132 *
1133 * @param deltaY The amount to scroll downwards, has to be negative.
1134 * @return The amount of scrolling to be performed by the scroller,
1135 * not handled by the overScroll amount.
1136 */
1137 private float overScrollDown(int deltaY) {
1138 deltaY = Math.min(deltaY, 0);
1139 float currentBottomAmount = getCurrentOverScrollAmount(false);
1140 float newBottomAmount = currentBottomAmount + deltaY;
1141 if (currentBottomAmount > 0) {
1142 setOverScrollAmount(newBottomAmount, false /* onTop */,
1143 false /* animate */);
1144 }
1145 // Bottom overScroll might not grab all scrolling motion,
1146 // we have to scroll as well.
1147 float scrollAmount = newBottomAmount < 0 ? newBottomAmount : 0.0f;
1148 float newScrollY = mOwnScrollY + scrollAmount;
1149 if (newScrollY < 0) {
1150 float currentTopPixels = getCurrentOverScrolledPixels(true);
1151 // We overScroll on the top
1152 setOverScrolledPixels(currentTopPixels - newScrollY,
1153 true /* onTop */,
1154 false /* animate */);
1155 mOwnScrollY = 0;
1156 scrollAmount = 0.0f;
1157 }
1158 return scrollAmount;
1159 }
1160
Selim Cinek67b22602014-03-10 15:40:16 +01001161 private void onSecondaryPointerUp(MotionEvent ev) {
1162 final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
1163 MotionEvent.ACTION_POINTER_INDEX_SHIFT;
1164 final int pointerId = ev.getPointerId(pointerIndex);
1165 if (pointerId == mActivePointerId) {
1166 // This was our active pointer going up. Choose a new
1167 // active pointer and adjust accordingly.
1168 // TODO: Make this decision more intelligent.
1169 final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
1170 mLastMotionY = (int) ev.getY(newPointerIndex);
1171 mActivePointerId = ev.getPointerId(newPointerIndex);
1172 if (mVelocityTracker != null) {
1173 mVelocityTracker.clear();
1174 }
1175 }
1176 }
1177
1178 private void initVelocityTrackerIfNotExists() {
1179 if (mVelocityTracker == null) {
1180 mVelocityTracker = VelocityTracker.obtain();
1181 }
1182 }
1183
1184 private void recycleVelocityTracker() {
1185 if (mVelocityTracker != null) {
1186 mVelocityTracker.recycle();
1187 mVelocityTracker = null;
1188 }
1189 }
1190
1191 private void initOrResetVelocityTracker() {
1192 if (mVelocityTracker == null) {
1193 mVelocityTracker = VelocityTracker.obtain();
1194 } else {
1195 mVelocityTracker.clear();
1196 }
1197 }
1198
1199 @Override
1200 public void computeScroll() {
1201 if (mScroller.computeScrollOffset()) {
1202 // This is called at drawing time by ViewGroup.
1203 int oldX = mScrollX;
1204 int oldY = mOwnScrollY;
1205 int x = mScroller.getCurrX();
1206 int y = mScroller.getCurrY();
1207
1208 if (oldX != x || oldY != y) {
1209 final int range = getScrollRange();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001210 if (y < 0 && oldY >= 0 || y > range && oldY <= range) {
1211 float currVelocity = mScroller.getCurrVelocity();
Selim Cinekba819622014-05-13 17:55:53 +02001212 if (currVelocity >= mMinimumVelocity) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001213 mMaxOverScroll = Math.abs(currVelocity) / 1000 * mOverflingDistance;
1214 }
1215 }
Selim Cinek67b22602014-03-10 15:40:16 +01001216
1217 overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001218 0, (int) (mMaxOverScroll), false);
Selim Cinek67b22602014-03-10 15:40:16 +01001219 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
Selim Cinek67b22602014-03-10 15:40:16 +01001220 }
1221
1222 // Keep on drawing until the animation has finished.
1223 postInvalidateOnAnimation();
1224 }
1225 }
1226
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001227 @Override
Selim Cinek4195dd02014-05-19 18:16:14 +02001228 protected boolean overScrollBy(int deltaX, int deltaY,
1229 int scrollX, int scrollY,
1230 int scrollRangeX, int scrollRangeY,
1231 int maxOverScrollX, int maxOverScrollY,
1232 boolean isTouchEvent) {
1233
1234 int newScrollY = scrollY + deltaY;
1235
1236 final int top = -maxOverScrollY;
1237 final int bottom = maxOverScrollY + scrollRangeY;
1238
1239 boolean clampedY = false;
1240 if (newScrollY > bottom) {
1241 newScrollY = bottom;
1242 clampedY = true;
1243 } else if (newScrollY < top) {
1244 newScrollY = top;
1245 clampedY = true;
1246 }
1247
1248 onOverScrolled(0, newScrollY, false, clampedY);
1249
1250 return clampedY;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001251 }
1252
1253 /**
1254 * Set the amount of overScrolled pixels which will force the view to apply a rubber-banded
1255 * overscroll effect based on numPixels. By default this will also cancel animations on the
1256 * same overScroll edge.
1257 *
1258 * @param numPixels The amount of pixels to overScroll by. These will be scaled according to
1259 * the rubber-banding logic.
1260 * @param onTop Should the effect be applied on top of the scroller.
1261 * @param animate Should an animation be performed.
1262 */
1263 public void setOverScrolledPixels(float numPixels, boolean onTop, boolean animate) {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001264 setOverScrollAmount(numPixels * getRubberBandFactor(onTop), onTop, animate, true);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001265 }
1266
1267 /**
1268 * Set the effective overScroll amount which will be directly reflected in the layout.
1269 * By default this will also cancel animations on the same overScroll edge.
1270 *
1271 * @param amount The amount to overScroll by.
1272 * @param onTop Should the effect be applied on top of the scroller.
1273 * @param animate Should an animation be performed.
1274 */
1275 public void setOverScrollAmount(float amount, boolean onTop, boolean animate) {
1276 setOverScrollAmount(amount, onTop, animate, true);
1277 }
1278
1279 /**
1280 * Set the effective overScroll amount which will be directly reflected in the layout.
1281 *
1282 * @param amount The amount to overScroll by.
1283 * @param onTop Should the effect be applied on top of the scroller.
1284 * @param animate Should an animation be performed.
1285 * @param cancelAnimators Should running animations be cancelled.
1286 */
1287 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1288 boolean cancelAnimators) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001289 setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
1290 }
1291
1292 /**
1293 * Set the effective overScroll amount which will be directly reflected in the layout.
1294 *
1295 * @param amount The amount to overScroll by.
1296 * @param onTop Should the effect be applied on top of the scroller.
1297 * @param animate Should an animation be performed.
1298 * @param cancelAnimators Should running animations be cancelled.
1299 * @param isRubberbanded The value which will be passed to
1300 * {@link OnOverscrollTopChangedListener#onOverscrollTopChanged}
1301 */
1302 public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
1303 boolean cancelAnimators, boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001304 if (cancelAnimators) {
1305 mStateAnimator.cancelOverScrollAnimators(onTop);
1306 }
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001307 setOverScrollAmountInternal(amount, onTop, animate, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001308 }
1309
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001310 private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
1311 boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001312 amount = Math.max(0, amount);
1313 if (animate) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001314 mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001315 } else {
Selim Cinekfed1ab62014-06-17 14:10:33 -07001316 setOverScrolledPixels(amount / getRubberBandFactor(onTop), onTop);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001317 mAmbientState.setOverScrollAmount(amount, onTop);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001318 if (onTop) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001319 notifyOverscrollTopListener(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001320 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001321 requestChildrenUpdate();
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001322 }
1323 }
1324
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001325 private void notifyOverscrollTopListener(float amount, boolean isRubberbanded) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001326 mExpandHelper.onlyObserveMovements(amount > 1.0f);
1327 if (mDontReportNextOverScroll) {
1328 mDontReportNextOverScroll = false;
1329 return;
1330 }
Jorim Jaggi290600a2014-05-30 17:02:20 +02001331 if (mOverscrollTopChangedListener != null) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001332 mOverscrollTopChangedListener.onOverscrollTopChanged(amount, isRubberbanded);
Jorim Jaggi290600a2014-05-30 17:02:20 +02001333 }
1334 }
1335
1336 public void setOverscrollTopChangedListener(
1337 OnOverscrollTopChangedListener overscrollTopChangedListener) {
1338 mOverscrollTopChangedListener = overscrollTopChangedListener;
1339 }
1340
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001341 public float getCurrentOverScrollAmount(boolean top) {
1342 return mAmbientState.getOverScrollAmount(top);
1343 }
1344
1345 public float getCurrentOverScrolledPixels(boolean top) {
1346 return top? mOverScrolledTopPixels : mOverScrolledBottomPixels;
1347 }
1348
1349 private void setOverScrolledPixels(float amount, boolean onTop) {
1350 if (onTop) {
1351 mOverScrolledTopPixels = amount;
1352 } else {
1353 mOverScrolledBottomPixels = amount;
1354 }
1355 }
1356
Selim Cinek319bdc42014-05-01 23:01:58 +02001357 private void customScrollTo(int y) {
Selim Cinek67b22602014-03-10 15:40:16 +01001358 mOwnScrollY = y;
Selim Cinek3af00cf2014-05-07 17:27:26 +02001359 updateChildren();
Selim Cinek67b22602014-03-10 15:40:16 +01001360 }
1361
1362 @Override
Selim Cinek319bdc42014-05-01 23:01:58 +02001363 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
Selim Cinek67b22602014-03-10 15:40:16 +01001364 // Treat animating scrolls differently; see #computeScroll() for why.
1365 if (!mScroller.isFinished()) {
1366 final int oldX = mScrollX;
1367 final int oldY = mOwnScrollY;
1368 mScrollX = scrollX;
1369 mOwnScrollY = scrollY;
Selim Cinek67b22602014-03-10 15:40:16 +01001370 if (clampedY) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001371 springBack();
1372 } else {
1373 onScrollChanged(mScrollX, mOwnScrollY, oldX, oldY);
1374 invalidateParentIfNeeded();
1375 updateChildren();
Jorim Jaggi290600a2014-05-30 17:02:20 +02001376 float overScrollTop = getCurrentOverScrollAmount(true);
1377 if (mOwnScrollY < 0) {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001378 notifyOverscrollTopListener(-mOwnScrollY, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001379 } else {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001380 notifyOverscrollTopListener(overScrollTop, isRubberbanded(true));
Jorim Jaggi290600a2014-05-30 17:02:20 +02001381 }
Selim Cinek67b22602014-03-10 15:40:16 +01001382 }
Selim Cinek67b22602014-03-10 15:40:16 +01001383 } else {
1384 customScrollTo(scrollY);
1385 scrollTo(scrollX, mScrollY);
1386 }
1387 }
1388
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001389 private void springBack() {
1390 int scrollRange = getScrollRange();
1391 boolean overScrolledTop = mOwnScrollY <= 0;
1392 boolean overScrolledBottom = mOwnScrollY >= scrollRange;
1393 if (overScrolledTop || overScrolledBottom) {
1394 boolean onTop;
1395 float newAmount;
1396 if (overScrolledTop) {
1397 onTop = true;
1398 newAmount = -mOwnScrollY;
1399 mOwnScrollY = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001400 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001401 } else {
1402 onTop = false;
1403 newAmount = mOwnScrollY - scrollRange;
1404 mOwnScrollY = scrollRange;
1405 }
1406 setOverScrollAmount(newAmount, onTop, false);
1407 setOverScrollAmount(0.0f, onTop, true);
1408 mScroller.forceFinished(true);
1409 }
1410 }
1411
Selim Cinek67b22602014-03-10 15:40:16 +01001412 private int getScrollRange() {
1413 int scrollRange = 0;
Jorim Jaggibe565df2014-04-28 17:51:23 +02001414 ExpandableView firstChild = (ExpandableView) getFirstChildNotGone();
Selim Cinek343e6e22014-04-11 21:23:30 +02001415 if (firstChild != null) {
Selim Cinek67b22602014-03-10 15:40:16 +01001416 int contentHeight = getContentHeight();
Selim Cineka5eaa602014-05-12 21:27:47 +02001417 scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
1418 + mBottomStackSlowDownHeight);
Selim Cinek4a1ac842014-05-01 15:51:58 +02001419 if (scrollRange > 0) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001420 int firstChildMaxExpandHeight = getMaxExpandHeight(firstChild);
Selim Cinek343e6e22014-04-11 21:23:30 +02001421 // We want to at least be able collapse the first item and not ending in a weird
1422 // end state.
Selim Cinek816c8e42015-11-19 12:00:45 -08001423 scrollRange = Math.max(scrollRange, firstChildMaxExpandHeight
1424 - firstChild.getMinHeight());
Selim Cinek343e6e22014-04-11 21:23:30 +02001425 }
Selim Cinek67b22602014-03-10 15:40:16 +01001426 }
1427 return scrollRange;
1428 }
1429
Selim Cinek343e6e22014-04-11 21:23:30 +02001430 /**
1431 * @return the first child which has visibility unequal to GONE
1432 */
Selim Cinekb55386d2015-12-16 17:26:49 -08001433 public ExpandableView getFirstChildNotGone() {
Selim Cinek343e6e22014-04-11 21:23:30 +02001434 int childCount = getChildCount();
1435 for (int i = 0; i < childCount; i++) {
1436 View child = getChildAt(i);
1437 if (child.getVisibility() != View.GONE) {
Selim Cinek816c8e42015-11-19 12:00:45 -08001438 return (ExpandableView) child;
Selim Cinek343e6e22014-04-11 21:23:30 +02001439 }
1440 }
1441 return null;
1442 }
1443
Selim Cinek4a1ac842014-05-01 15:51:58 +02001444 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001445 * @return The first child which has visibility unequal to GONE which is currently below the
1446 * given translationY or equal to it.
1447 */
1448 private View getFirstChildBelowTranlsationY(float translationY) {
1449 int childCount = getChildCount();
1450 for (int i = 0; i < childCount; i++) {
1451 View child = getChildAt(i);
1452 if (child.getVisibility() != View.GONE && child.getTranslationY() >= translationY) {
1453 return child;
1454 }
1455 }
1456 return null;
1457 }
1458
1459 /**
Selim Cinek4a1ac842014-05-01 15:51:58 +02001460 * @return the last child which has visibility unequal to GONE
1461 */
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001462 public View getLastChildNotGone() {
Selim Cinek4a1ac842014-05-01 15:51:58 +02001463 int childCount = getChildCount();
1464 for (int i = childCount - 1; i >= 0; i--) {
1465 View child = getChildAt(i);
1466 if (child.getVisibility() != View.GONE) {
1467 return child;
1468 }
1469 }
1470 return null;
1471 }
1472
Jorim Jaggi069cd032014-05-15 03:09:01 +02001473 /**
1474 * @return the number of children which have visibility unequal to GONE
1475 */
1476 public int getNotGoneChildCount() {
1477 int childCount = getChildCount();
1478 int count = 0;
1479 for (int i = 0; i < childCount; i++) {
Selim Cinek2cd45df2015-06-09 18:00:07 -07001480 ExpandableView child = (ExpandableView) getChildAt(i);
1481 if (child.getVisibility() != View.GONE && !child.willBeGone()) {
Jorim Jaggi069cd032014-05-15 03:09:01 +02001482 count++;
1483 }
1484 }
1485 return count;
1486 }
1487
Selim Cinek343e6e22014-04-11 21:23:30 +02001488 private int getMaxExpandHeight(View view) {
1489 if (view instanceof ExpandableNotificationRow) {
1490 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Jorim Jaggi9cbadd32014-05-01 20:18:31 +02001491 return row.getIntrinsicHeight();
Selim Cinek343e6e22014-04-11 21:23:30 +02001492 }
1493 return view.getHeight();
1494 }
1495
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001496 public int getContentHeight() {
Selim Cinek67b22602014-03-10 15:40:16 +01001497 return mContentHeight;
1498 }
1499
1500 private void updateContentHeight() {
1501 int height = 0;
Selim Cinek61633a82016-01-25 15:54:10 -08001502 boolean previousNeedsIncreasedPaddings = false;
Selim Cinek67b22602014-03-10 15:40:16 +01001503 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08001504 ExpandableView expandableView = (ExpandableView) getChildAt(i);
1505 if (expandableView.getVisibility() != View.GONE) {
1506 boolean needsIncreasedPaddings = expandableView.needsIncreasedPadding();
Jorim Jaggibe565df2014-04-28 17:51:23 +02001507 if (height != 0) {
Selim Cinek61633a82016-01-25 15:54:10 -08001508 int padding = needsIncreasedPaddings || previousNeedsIncreasedPaddings
1509 ? mIncreasedPaddingBetweenElements
1510 : mPaddingBetweenElements;
1511 height += padding;
Jorim Jaggid4a57442014-04-10 02:45:55 +02001512 }
Selim Cinek61633a82016-01-25 15:54:10 -08001513 previousNeedsIncreasedPaddings = needsIncreasedPaddings;
1514 height += expandableView.getIntrinsicHeight();
Selim Cinek67b22602014-03-10 15:40:16 +01001515 }
1516 }
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02001517 mContentHeight = height + mTopPadding;
Selim Cinek67b22602014-03-10 15:40:16 +01001518 }
1519
Selim Cinek6811d722016-01-19 17:53:12 -08001520 private void updateBackground() {
1521 if (mAmbientState.isDark()) {
1522 return;
1523 }
1524 updateBackgroundBounds();
1525 if (!mCurrentBounds.equals(mBackgroundBounds)) {
Selim Cinek614576e2016-01-20 10:54:09 -08001526 if (mAnimateNextBackgroundTop || mAnimateNextBackgroundBottom || areBoundsAnimating()) {
1527 startBackgroundAnimation();
1528 } else {
1529 mCurrentBounds.set(mBackgroundBounds);
1530 applyCurrentBackgroundBounds();
1531 }
1532 } else {
1533 if (mBottomAnimator != null) {
1534 mBottomAnimator.cancel();
1535 }
1536 if (mTopAnimator != null) {
1537 mTopAnimator.cancel();
1538 }
Selim Cinek6811d722016-01-19 17:53:12 -08001539 }
Selim Cinek614576e2016-01-20 10:54:09 -08001540 mAnimateNextBackgroundBottom = false;
1541 mAnimateNextBackgroundTop = false;
1542 }
1543
1544 private boolean areBoundsAnimating() {
1545 return mBottomAnimator != null || mTopAnimator != null;
1546 }
1547
1548 private void startBackgroundAnimation() {
1549 startBottomAnimation();
1550 startTopAnimation();
1551 }
1552
1553 private void startTopAnimation() {
1554 int previousEndValue = mEndAnimationRect.top;
1555 int newEndValue = mBackgroundBounds.top;
1556 ObjectAnimator previousAnimator = mTopAnimator;
1557 if (previousAnimator != null && previousEndValue == newEndValue) {
1558 return;
1559 }
1560 if (!mAnimateNextBackgroundTop) {
1561 // just a local update was performed
1562 if (previousAnimator != null) {
1563 // we need to increase all animation keyframes of the previous animator by the
1564 // relative change to the end value
1565 int previousStartValue = mStartAnimationRect.top;
1566 PropertyValuesHolder[] values = previousAnimator.getValues();
1567 values[0].setIntValues(previousStartValue, newEndValue);
1568 mStartAnimationRect.top = previousStartValue;
1569 mEndAnimationRect.top = newEndValue;
1570 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1571 return;
1572 } else {
1573 // no new animation needed, let's just apply the value
1574 setBackgroundTop(newEndValue);
1575 return;
1576 }
1577 }
1578 if (previousAnimator != null) {
1579 previousAnimator.cancel();
1580 }
1581 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
1582 mCurrentBounds.top, newEndValue);
1583 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1584 animator.setInterpolator(interpolator);
1585 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1586 // remove the tag when the animation is finished
1587 animator.addListener(new AnimatorListenerAdapter() {
1588 @Override
1589 public void onAnimationEnd(Animator animation) {
1590 mStartAnimationRect.top = -1;
1591 mEndAnimationRect.top = -1;
1592 mTopAnimator = null;
1593 }
1594 });
1595 animator.start();
1596 mStartAnimationRect.top = mCurrentBounds.top;
1597 mEndAnimationRect.top = newEndValue;
1598 mTopAnimator = animator;
1599 }
1600
1601 private void startBottomAnimation() {
1602 int previousStartValue = mStartAnimationRect.bottom;
1603 int previousEndValue = mEndAnimationRect.bottom;
1604 int newEndValue = mBackgroundBounds.bottom;
1605 ObjectAnimator previousAnimator = mBottomAnimator;
1606 if (previousAnimator != null && previousEndValue == newEndValue) {
1607 return;
1608 }
1609 if (!mAnimateNextBackgroundBottom) {
1610 // just a local update was performed
1611 if (previousAnimator != null) {
1612 // we need to increase all animation keyframes of the previous animator by the
1613 // relative change to the end value
1614 PropertyValuesHolder[] values = previousAnimator.getValues();
1615 values[0].setIntValues(previousStartValue, newEndValue);
1616 mStartAnimationRect.bottom = previousStartValue;
1617 mEndAnimationRect.bottom = newEndValue;
1618 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
1619 return;
1620 } else {
1621 // no new animation needed, let's just apply the value
1622 setBackgroundBottom(newEndValue);
1623 return;
1624 }
1625 }
1626 if (previousAnimator != null) {
1627 previousAnimator.cancel();
1628 }
1629 ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
1630 mCurrentBounds.bottom, newEndValue);
1631 Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
1632 animator.setInterpolator(interpolator);
1633 animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
1634 // remove the tag when the animation is finished
1635 animator.addListener(new AnimatorListenerAdapter() {
1636 @Override
1637 public void onAnimationEnd(Animator animation) {
1638 mStartAnimationRect.bottom = -1;
1639 mEndAnimationRect.bottom = -1;
1640 mBottomAnimator = null;
1641 }
1642 });
1643 animator.start();
1644 mStartAnimationRect.bottom = mCurrentBounds.bottom;
1645 mEndAnimationRect.bottom = newEndValue;
1646 mBottomAnimator = animator;
1647 }
1648
1649 private void setBackgroundTop(int top) {
1650 mCurrentBounds.top = top;
1651 applyCurrentBackgroundBounds();
1652 }
1653
1654 public void setBackgroundBottom(int bottom) {
1655 mCurrentBounds.bottom = bottom;
1656 applyCurrentBackgroundBounds();
1657 }
1658
1659 private void applyCurrentBackgroundBounds() {
1660 mScrimController.setExcludedBackgroundArea(mCurrentBounds);
Selim Cinek614576e2016-01-20 10:54:09 -08001661 invalidate();
Selim Cinek6811d722016-01-19 17:53:12 -08001662 }
1663
1664 /**
1665 * Update the background bounds to the new desired bounds
1666 */
1667 private void updateBackgroundBounds() {
Selim Cinek614576e2016-01-20 10:54:09 -08001668 mBackgroundBounds.left = (int) getX();
1669 mBackgroundBounds.right = (int) (getX() + getWidth());
1670 if (!mIsExpanded) {
1671 mBackgroundBounds.top = 0;
1672 mBackgroundBounds.bottom = 0;
1673 }
1674 ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001675 int top = 0;
1676 if (firstView != null) {
1677 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView);
Selim Cinek614576e2016-01-20 10:54:09 -08001678 if (mAnimateNextBackgroundTop
1679 || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
1680 || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
Selim Cinek6811d722016-01-19 17:53:12 -08001681 // we're ending up at the same location as we are now, lets just skip the animation
1682 top = finalTranslationY;
1683 } else {
1684 top = (int) firstView.getTranslationY();
1685 }
1686 }
Selim Cinek614576e2016-01-20 10:54:09 -08001687 ActivatableNotificationView lastView = mLastVisibleBackgroundChild;
Selim Cinek6811d722016-01-19 17:53:12 -08001688 int bottom = 0;
1689 if (lastView != null) {
1690 int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView);
1691 int finalHeight = StackStateAnimator.getFinalActualHeight(lastView);
1692 int finalBottom = finalTranslationY + finalHeight;
1693 finalBottom = Math.min(finalBottom, getHeight());
Selim Cinek614576e2016-01-20 10:54:09 -08001694 if (mAnimateNextBackgroundBottom
1695 || mBottomAnimator == null && mCurrentBounds.bottom == finalBottom
1696 || mBottomAnimator != null && mEndAnimationRect.bottom == finalBottom) {
Selim Cinek6811d722016-01-19 17:53:12 -08001697 // we're ending up at the same location as we are now, lets just skip the animation
1698 bottom = finalBottom;
1699 } else {
1700 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
1701 bottom = Math.min(bottom, getHeight());
1702 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001703 } else {
1704 top = (int) (mTopPadding + mStackTranslation);
Selim Cinek7db42982016-02-02 15:21:41 -08001705 bottom = top;
Selim Cinek6811d722016-01-19 17:53:12 -08001706 }
Selim Cinek3776fe02016-02-04 13:32:43 -08001707 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
1708 mBackgroundBounds.top = (int) Math.max(mTopPadding + mStackTranslation, top);
1709 } else {
1710 // otherwise the animation from the shade to the keyguard will jump as it's maxed
1711 mBackgroundBounds.top = Math.max(0, top);
1712 }
1713 mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
Selim Cinek6811d722016-01-19 17:53:12 -08001714 }
1715
Selim Cinek614576e2016-01-20 10:54:09 -08001716 private ActivatableNotificationView getFirstPinnedHeadsUp() {
1717 int childCount = getChildCount();
1718 for (int i = 0; i < childCount; i++) {
1719 View child = getChildAt(i);
1720 if (child.getVisibility() != View.GONE
1721 && child instanceof ExpandableNotificationRow) {
1722 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
1723 if (row.isPinned()) {
1724 return row;
1725 }
1726 }
1727 }
1728 return null;
1729 }
1730
1731 private ActivatableNotificationView getLastChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001732 int childCount = getChildCount();
1733 for (int i = childCount - 1; i >= 0; i--) {
1734 View child = getChildAt(i);
1735 if (child.getVisibility() != View.GONE
1736 && child instanceof ActivatableNotificationView) {
1737 return (ActivatableNotificationView) child;
1738 }
1739 }
1740 return null;
1741 }
1742
Selim Cinek614576e2016-01-20 10:54:09 -08001743 private ActivatableNotificationView getFirstChildWithBackground() {
Selim Cinek6811d722016-01-19 17:53:12 -08001744 int childCount = getChildCount();
1745 for (int i = 0; i < childCount; i++) {
1746 View child = getChildAt(i);
1747 if (child.getVisibility() != View.GONE
1748 && child instanceof ActivatableNotificationView) {
1749 return (ActivatableNotificationView) child;
1750 }
1751 }
1752 return null;
1753 }
1754
Selim Cinek67b22602014-03-10 15:40:16 +01001755 /**
1756 * Fling the scroll view
1757 *
1758 * @param velocityY The initial velocity in the Y direction. Positive
1759 * numbers mean that the finger/cursor is moving down the screen,
1760 * which means we want to scroll towards the top.
1761 */
1762 private void fling(int velocityY) {
1763 if (getChildCount() > 0) {
Selim Cinek4195dd02014-05-19 18:16:14 +02001764 int scrollRange = getScrollRange();
Selim Cinek67b22602014-03-10 15:40:16 +01001765
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001766 float topAmount = getCurrentOverScrollAmount(true);
1767 float bottomAmount = getCurrentOverScrollAmount(false);
1768 if (velocityY < 0 && topAmount > 0) {
1769 mOwnScrollY -= (int) topAmount;
Selim Cinek1408eb52014-06-02 14:45:38 +02001770 mDontReportNextOverScroll = true;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001771 setOverScrollAmount(0, true, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001772 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001773 * mOverflingDistance + topAmount;
1774 } else if (velocityY > 0 && bottomAmount > 0) {
1775 mOwnScrollY += bottomAmount;
1776 setOverScrollAmount(0, false, false);
Selim Cinekfed1ab62014-06-17 14:10:33 -07001777 mMaxOverScroll = Math.abs(velocityY) / 1000f
1778 * getRubberBandFactor(false /* onTop */) * mOverflingDistance
1779 + bottomAmount;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001780 } else {
1781 // it will be set once we reach the boundary
1782 mMaxOverScroll = 0.0f;
1783 }
1784 mScroller.fling(mScrollX, mOwnScrollY, 1, velocityY, 0, 0, 0,
Selim Cinek4195dd02014-05-19 18:16:14 +02001785 Math.max(0, scrollRange), 0, Integer.MAX_VALUE / 2);
Selim Cinek67b22602014-03-10 15:40:16 +01001786
1787 postInvalidateOnAnimation();
1788 }
1789 }
1790
Selim Cinek1408eb52014-06-02 14:45:38 +02001791 /**
1792 * @return Whether a fling performed on the top overscroll edge lead to the expanded
1793 * overScroll view (i.e QS).
1794 */
1795 private boolean shouldOverScrollFling(int initialVelocity) {
1796 float topOverScroll = getCurrentOverScrollAmount(true);
1797 return mScrolledToTopOnFirstDown
1798 && !mExpandedInThisMotion
1799 && topOverScroll > mMinTopOverScrollToEscape
1800 && initialVelocity > 0;
1801 }
1802
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001803 /**
1804 * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
1805 * account.
1806 *
1807 * @param qsHeight the top padding imposed by the quick settings panel
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001808 * @param animate whether to animate the change
1809 * @param ignoreIntrinsicPadding if true, {@link #getIntrinsicPadding()} is ignored and
1810 * {@code qsHeight} is the final top padding
1811 */
Jason Monk16ac3772016-02-10 15:39:21 -05001812 public void updateTopPadding(float qsHeight, boolean animate,
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001813 boolean ignoreIntrinsicPadding) {
Jason Monk16ac3772016-02-10 15:39:21 -05001814 float start = qsHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001815 float stackHeight = getHeight() - start;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001816 int minStackHeight = getMinStackHeight();
1817 if (stackHeight <= minStackHeight) {
1818 float overflow = minStackHeight - stackHeight;
1819 stackHeight = minStackHeight;
Selim Cinek1408eb52014-06-02 14:45:38 +02001820 start = getHeight() - stackHeight;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001821 mTopPaddingOverflow = overflow;
Selim Cinek1408eb52014-06-02 14:45:38 +02001822 } else {
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001823 mTopPaddingOverflow = 0;
Selim Cinek1408eb52014-06-02 14:45:38 +02001824 }
Jorim Jaggi06a0c3a2014-10-29 17:17:21 +01001825 setTopPadding(ignoreIntrinsicPadding ? (int) start : clampPadding((int) start),
1826 animate);
1827 setStackHeight(mLastSetStackHeight);
Selim Cinek1408eb52014-06-02 14:45:38 +02001828 }
1829
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001830 public int getMinStackHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001831 final ExpandableView firstChild = getFirstChildNotGone();
Selim Cinek31aada42015-12-18 17:51:15 -08001832 final int firstChildMinHeight = firstChild != null ? firstChild.getMinHeight()
Selim Cinek816c8e42015-11-19 12:00:45 -08001833 : mCollapsedSize;
1834 return firstChildMinHeight + mBottomStackPeekSize + mCollapseSecondCardPadding;
Jorim Jaggi30c305c2014-07-01 23:34:41 +02001835 }
1836
1837 public float getTopPaddingOverflow() {
1838 return mTopPaddingOverflow;
1839 }
1840
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001841 public int getPeekHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -08001842 final ExpandableView firstChild = getFirstChildNotGone();
1843 final int firstChildMinHeight = firstChild != null ? (int) firstChild.getMinHeight()
1844 : mCollapsedSize;
1845 return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02001846 + mCollapseSecondCardPadding;
Jorim Jaggi2580a9762014-06-25 03:08:25 +02001847 }
1848
Selim Cinek1408eb52014-06-02 14:45:38 +02001849 private int clampPadding(int desiredPadding) {
1850 return Math.max(desiredPadding, mIntrinsicPadding);
1851 }
1852
Selim Cinekfed1ab62014-06-17 14:10:33 -07001853 private float getRubberBandFactor(boolean onTop) {
1854 if (!onTop) {
1855 return RUBBER_BAND_FACTOR_NORMAL;
1856 }
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001857 if (mExpandedInThisMotion) {
1858 return RUBBER_BAND_FACTOR_AFTER_EXPAND;
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001859 } else if (mIsExpansionChanging || mPanelTracking) {
Jorim Jaggi47c85a32014-06-05 17:25:40 +02001860 return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
1861 } else if (mScrolledToTopOnFirstDown) {
1862 return 1.0f;
1863 }
1864 return RUBBER_BAND_FACTOR_NORMAL;
Selim Cinek1408eb52014-06-02 14:45:38 +02001865 }
1866
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001867 /**
1868 * Accompanying function for {@link #getRubberBandFactor}: Returns true if the overscroll is
1869 * rubberbanded, false if it is technically an overscroll but rather a motion to expand the
1870 * overscroll view (e.g. expand QS).
1871 */
1872 private boolean isRubberbanded(boolean onTop) {
Jorim Jaggie4b840d2015-06-30 16:19:17 -07001873 return !onTop || mExpandedInThisMotion || mIsExpansionChanging || mPanelTracking
Jorim Jaggi475b21d2014-07-01 18:13:24 +02001874 || !mScrolledToTopOnFirstDown;
1875 }
1876
Selim Cinek67b22602014-03-10 15:40:16 +01001877 private void endDrag() {
1878 setIsBeingDragged(false);
1879
1880 recycleVelocityTracker();
1881
Selim Cinek8d9ff9c2014-05-12 15:13:04 +02001882 if (getCurrentOverScrollAmount(true /* onTop */) > 0) {
1883 setOverScrollAmount(0, true /* onTop */, true /* animate */);
1884 }
1885 if (getCurrentOverScrollAmount(false /* onTop */) > 0) {
1886 setOverScrollAmount(0, false /* onTop */, true /* animate */);
1887 }
Selim Cinek67b22602014-03-10 15:40:16 +01001888 }
1889
Jorim Jaggi56306252014-07-03 00:40:09 +02001890 private void transformTouchEvent(MotionEvent ev, View sourceView, View targetView) {
1891 ev.offsetLocation(sourceView.getX(), sourceView.getY());
1892 ev.offsetLocation(-targetView.getX(), -targetView.getY());
1893 }
1894
Selim Cinek67b22602014-03-10 15:40:16 +01001895 @Override
1896 public boolean onInterceptTouchEvent(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001897 initDownStates(ev);
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001898 handleEmptySpaceClick(ev);
Selim Cinek1408eb52014-06-02 14:45:38 +02001899 boolean expandWantsIt = false;
Selim Cinekcb9400a2015-06-03 16:56:13 +02001900 if (!mSwipingInProgress && !mOnlyScrollingInThisMotion) {
Selim Cinek1408eb52014-06-02 14:45:38 +02001901 expandWantsIt = mExpandHelper.onInterceptTouchEvent(ev);
1902 }
Selim Cinek67b22602014-03-10 15:40:16 +01001903 boolean scrollWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001904 if (!mSwipingInProgress && !mExpandingNotification) {
Selim Cinek67b22602014-03-10 15:40:16 +01001905 scrollWantsIt = onInterceptTouchEventScroll(ev);
1906 }
1907 boolean swipeWantsIt = false;
Selim Cinek1408eb52014-06-02 14:45:38 +02001908 if (!mIsBeingDragged
1909 && !mExpandingNotification
1910 && !mExpandedInThisMotion
1911 && !mOnlyScrollingInThisMotion) {
Selim Cinek67b22602014-03-10 15:40:16 +01001912 swipeWantsIt = mSwipeHelper.onInterceptTouchEvent(ev);
1913 }
Selim Cinek1408eb52014-06-02 14:45:38 +02001914 return swipeWantsIt || scrollWantsIt || expandWantsIt || super.onInterceptTouchEvent(ev);
1915 }
1916
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001917 private void handleEmptySpaceClick(MotionEvent ev) {
1918 switch (ev.getActionMasked()) {
1919 case MotionEvent.ACTION_MOVE:
1920 if (mTouchIsClick && (Math.abs(ev.getY() - mInitialTouchY) > mTouchSlop
1921 || Math.abs(ev.getX() - mInitialTouchX) > mTouchSlop )) {
1922 mTouchIsClick = false;
1923 }
1924 break;
1925 case MotionEvent.ACTION_UP:
1926 if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
1927 isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
1928 mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
1929 }
1930 break;
1931 }
1932 }
1933
Selim Cinek1408eb52014-06-02 14:45:38 +02001934 private void initDownStates(MotionEvent ev) {
1935 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
1936 mExpandedInThisMotion = false;
1937 mOnlyScrollingInThisMotion = !mScroller.isFinished();
Selim Cinekf7a14c02014-07-07 14:01:46 +02001938 mDisallowScrollingInThisMotion = false;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01001939 mTouchIsClick = true;
1940 mInitialTouchX = ev.getX();
1941 mInitialTouchY = ev.getY();
Selim Cinek1408eb52014-06-02 14:45:38 +02001942 }
Selim Cinek67b22602014-03-10 15:40:16 +01001943 }
1944
Selim Cinekef5127e2015-12-21 16:55:58 -08001945 public void setChildTransferInProgress(boolean childTransferInProgress) {
1946 mChildTransferInProgress = childTransferInProgress;
1947 }
1948
Christoph Studer068f5922014-04-08 17:43:07 -04001949 @Override
Adam Powell6690d012015-06-17 16:41:56 -07001950 public void onViewRemoved(View child) {
Christoph Studer068f5922014-04-08 17:43:07 -04001951 super.onViewRemoved(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01001952 // we only call our internal methods if this is actually a removal and not just a
1953 // notification which becomes a child notification
Selim Cinekef5127e2015-12-21 16:55:58 -08001954 if (!mChildTransferInProgress) {
Selim Cinekb5605e52015-02-20 18:21:41 +01001955 onViewRemovedInternal(child);
1956 }
1957 }
1958
1959 private void onViewRemovedInternal(View child) {
Selim Cinekb55386d2015-12-16 17:26:49 -08001960 mStackScrollAlgorithm.notifyChildrenChanged(this);
Selim Cinek159ffdb2014-06-04 22:24:18 +02001961 if (mChangePositionInProgress) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001962 // This is only a position change, don't do anything special
1963 return;
1964 }
Jorim Jaggibe565df2014-04-28 17:51:23 +02001965 ((ExpandableView) child).setOnHeightChangedListener(null);
Christoph Studer068f5922014-04-08 17:43:07 -04001966 mCurrentStackScrollState.removeViewStateForView(child);
Selim Cinek61633a82016-01-25 15:54:10 -08001967 updateScrollStateForRemovedChild((ExpandableView) child);
Selim Cinek2aab2fb2015-04-15 18:47:01 -07001968 boolean animationGenerated = generateRemoveAnimation(child);
1969 if (animationGenerated && !mSwipedOutViews.contains(child)) {
1970 // Add this view to an overlay in order to ensure that it will still be temporary
1971 // drawn when removed
1972 getOverlay().add(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001973 }
Selim Cinekcab4a602014-09-03 14:47:57 +02001974 updateAnimationState(false, child);
Selim Cinekc0f4c012014-08-25 15:45:33 +02001975
1976 // Make sure the clipRect we might have set is removed
Selim Cineka272dfe2015-02-20 18:12:28 +01001977 ((ExpandableView) child).setClipTopOptimization(0);
Selim Cinekc27437b2014-05-14 10:23:33 +02001978 }
1979
Selim Cinekb5605e52015-02-20 18:21:41 +01001980 private boolean isChildInGroup(View child) {
1981 return child instanceof ExpandableNotificationRow
1982 && mGroupManager.isChildInGroupWithSummary(
1983 ((ExpandableNotificationRow) child).getStatusBarNotification());
1984 }
1985
Selim Cinek8efa6dd2014-05-19 16:27:37 +02001986 /**
1987 * Generate a remove animation for a child view.
1988 *
1989 * @param child The view to generate the remove animation for.
1990 * @return Whether an animation was generated.
1991 */
1992 private boolean generateRemoveAnimation(View child) {
Selim Cineke0890e52015-06-17 11:17:08 -07001993 if (removeRemovedChildFromHeadsUpChangeAnimations(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07001994 mAddedHeadsUpChildren.remove(child);
1995 return false;
1996 }
Selim Cinek0fccc722015-07-29 17:04:36 -07001997 if (isClickedHeadsUp(child)) {
1998 // An animation is already running, add it to the Overlay
1999 mClearOverlayViewsWhenFinished.add(child);
2000 return true;
2001 }
Selim Cinekb5605e52015-02-20 18:21:41 +01002002 if (mIsExpanded && mAnimationsEnabled && !isChildInInvisibleGroup(child)) {
Selim Cinek233241f2015-06-01 06:11:19 -07002003 if (!mChildrenToAddAnimated.contains(child)) {
Selim Cinekf4c19962014-05-01 21:55:31 +02002004 // Generate Animations
2005 mChildrenToRemoveAnimated.add(child);
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002006 mNeedsAnimation = true;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002007 return true;
Selim Cinekf4c19962014-05-01 21:55:31 +02002008 } else {
2009 mChildrenToAddAnimated.remove(child);
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002010 mFromMoreCardAdditions.remove(child);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002011 return false;
Selim Cinekf4c19962014-05-01 21:55:31 +02002012 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002013 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002014 return false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002015 }
2016
Selim Cinek0fccc722015-07-29 17:04:36 -07002017 private boolean isClickedHeadsUp(View child) {
2018 return HeadsUpManager.isClickedHeadsUpNotification(child);
2019 }
2020
Selim Cineke0890e52015-06-17 11:17:08 -07002021 /**
2022 * Remove a removed child view from the heads up animations if it was just added there
2023 *
2024 * @return whether any child was removed from the list to animate
2025 */
2026 private boolean removeRemovedChildFromHeadsUpChangeAnimations(View child) {
2027 boolean hasAddEvent = false;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002028 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
2029 ExpandableNotificationRow row = eventPair.first;
Selim Cineke0890e52015-06-17 11:17:08 -07002030 boolean isHeadsUp = eventPair.second;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002031 if (child == row) {
Selim Cineke0890e52015-06-17 11:17:08 -07002032 mTmpList.add(eventPair);
2033 hasAddEvent |= isHeadsUp;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002034 }
2035 }
Selim Cineke0890e52015-06-17 11:17:08 -07002036 if (hasAddEvent) {
2037 // This child was just added lets remove all events.
2038 mHeadsUpChangeAnimations.removeAll(mTmpList);
2039 }
2040 mTmpList.clear();
2041 return hasAddEvent;
Selim Cinekffa6eb82015-05-21 12:11:12 -07002042 }
2043
Selim Cinek572bbd42014-04-25 16:43:27 +02002044 /**
Selim Cinekb5605e52015-02-20 18:21:41 +01002045 * @param child the child to query
2046 * @return whether a view is not a top level child but a child notification and that group is
2047 * not expanded
2048 */
2049 private boolean isChildInInvisibleGroup(View child) {
2050 if (child instanceof ExpandableNotificationRow) {
2051 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2052 ExpandableNotificationRow groupSummary =
2053 mGroupManager.getGroupSummary(row.getStatusBarNotification());
2054 if (groupSummary != null && groupSummary != row) {
Selim Cinek83bc7832015-10-22 13:26:54 -07002055 return row.getVisibility() == View.INVISIBLE;
Selim Cinekb5605e52015-02-20 18:21:41 +01002056 }
2057 }
2058 return false;
2059 }
2060
2061 /**
Selim Cinek572bbd42014-04-25 16:43:27 +02002062 * Updates the scroll position when a child was removed
2063 *
2064 * @param removedChild the removed child
2065 */
Selim Cinek61633a82016-01-25 15:54:10 -08002066 private void updateScrollStateForRemovedChild(ExpandableView removedChild) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002067 int startingPosition = getPositionInLinearLayout(removedChild);
Selim Cinek61633a82016-01-25 15:54:10 -08002068 int padding = removedChild.needsIncreasedPadding()
2069 ? mIncreasedPaddingBetweenElements :
2070 mPaddingBetweenElements;
2071 int childHeight = getIntrinsicHeight(removedChild) + padding;
Selim Cinek572bbd42014-04-25 16:43:27 +02002072 int endPosition = startingPosition + childHeight;
2073 if (endPosition <= mOwnScrollY) {
2074 // This child is fully scrolled of the top, so we have to deduct its height from the
2075 // scrollPosition
2076 mOwnScrollY -= childHeight;
2077 } else if (startingPosition < mOwnScrollY) {
2078 // This child is currently being scrolled into, set the scroll position to the start of
2079 // this child
2080 mOwnScrollY = startingPosition;
2081 }
2082 }
2083
Selim Cinekd7c4e002014-07-04 18:36:42 +02002084 private int getIntrinsicHeight(View view) {
2085 if (view instanceof ExpandableView) {
2086 ExpandableView expandableView = (ExpandableView) view;
2087 return expandableView.getIntrinsicHeight();
2088 }
2089 return view.getHeight();
2090 }
2091
Selim Cinek572bbd42014-04-25 16:43:27 +02002092 private int getPositionInLinearLayout(View requestedChild) {
2093 int position = 0;
Selim Cinek61633a82016-01-25 15:54:10 -08002094 boolean previousNeedsIncreasedPaddings = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002095 for (int i = 0; i < getChildCount(); i++) {
Selim Cinek61633a82016-01-25 15:54:10 -08002096 ExpandableView child = (ExpandableView) getChildAt(i);
2097 boolean notGone = child.getVisibility() != View.GONE;
2098 if (notGone) {
2099 boolean needsIncreasedPaddings = child.needsIncreasedPadding();
2100 if (position != 0) {
2101 int padding = needsIncreasedPaddings || previousNeedsIncreasedPaddings
2102 ? mIncreasedPaddingBetweenElements :
2103 mPaddingBetweenElements;
2104 position += padding;
2105 }
2106 previousNeedsIncreasedPaddings = needsIncreasedPaddings;
2107 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002108 if (child == requestedChild) {
2109 return position;
2110 }
Selim Cinek61633a82016-01-25 15:54:10 -08002111 if (notGone) {
Selim Cinekabdc5a02014-09-02 13:46:00 +02002112 position += getIntrinsicHeight(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002113 }
2114 }
2115 return 0;
Selim Cinek1685e632014-04-08 02:27:49 +02002116 }
2117
2118 @Override
Adam Powell6690d012015-06-17 16:41:56 -07002119 public void onViewAdded(View child) {
Selim Cinek1685e632014-04-08 02:27:49 +02002120 super.onViewAdded(child);
Selim Cinekb5605e52015-02-20 18:21:41 +01002121 onViewAddedInternal(child);
2122 }
2123
Selim Cinek614576e2016-01-20 10:54:09 -08002124 private void updateFirstAndLastBackgroundViews() {
2125 ActivatableNotificationView firstChild = getFirstChildWithBackground();
2126 ActivatableNotificationView lastChild = getLastChildWithBackground();
2127 if (mAnimationsEnabled && mIsExpanded) {
2128 mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
2129 mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
2130 } else {
2131 mAnimateNextBackgroundTop = false;
2132 mAnimateNextBackgroundBottom = false;
2133 }
2134 mFirstVisibleBackgroundChild = firstChild;
2135 mLastVisibleBackgroundChild = lastChild;
2136 }
2137
Selim Cinekb5605e52015-02-20 18:21:41 +01002138 private void onViewAddedInternal(View child) {
Selim Cinekd06c41c2015-07-06 14:51:36 -07002139 updateHideSensitiveForChild(child);
Selim Cinekb55386d2015-12-16 17:26:49 -08002140 mStackScrollAlgorithm.notifyChildrenChanged(this);
Jorim Jaggibe565df2014-04-28 17:51:23 +02002141 ((ExpandableView) child).setOnHeightChangedListener(this);
Jorim Jaggif6411742014-08-05 17:10:43 +00002142 generateAddAnimation(child, false /* fromMoreCard */);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002143 updateAnimationState(child);
Selim Cinek98713a42015-09-21 15:47:20 +02002144 updateChronometerForChild(child);
Selim Cinek572bbd42014-04-25 16:43:27 +02002145 }
2146
Selim Cinekd06c41c2015-07-06 14:51:36 -07002147 private void updateHideSensitiveForChild(View child) {
2148 if (mAmbientState.isHideSensitive() && child instanceof ExpandableView) {
2149 ExpandableView expandableView = (ExpandableView) child;
2150 expandableView.setHideSensitiveForIntrinsicHeight(true);
2151 }
2152 }
2153
Selim Cinekb5605e52015-02-20 18:21:41 +01002154 public void notifyGroupChildRemoved(View row) {
2155 onViewRemovedInternal(row);
2156 }
2157
2158 public void notifyGroupChildAdded(View row) {
2159 onViewAddedInternal(row);
2160 }
2161
Jorim Jaggi75c95042014-05-16 19:09:59 +02002162 public void setAnimationsEnabled(boolean animationsEnabled) {
2163 mAnimationsEnabled = animationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002164 updateNotificationAnimationStates();
2165 }
2166
2167 private void updateNotificationAnimationStates() {
Selim Cinek8d490d42015-04-10 00:05:50 -07002168 boolean running = mAnimationsEnabled;
Selim Cinekcab4a602014-09-03 14:47:57 +02002169 int childCount = getChildCount();
2170 for (int i = 0; i < childCount; i++) {
2171 View child = getChildAt(i);
Selim Cinek8d490d42015-04-10 00:05:50 -07002172 running &= mIsExpanded || isPinnedHeadsUp(child);
Selim Cinekcab4a602014-09-03 14:47:57 +02002173 updateAnimationState(running, child);
2174 }
2175 }
2176
Selim Cinek51ae05d2014-09-09 15:51:38 +02002177 private void updateAnimationState(View child) {
Selim Cinek01af3342016-02-09 19:25:31 -08002178 updateAnimationState(mAnimationsEnabled && (mIsExpanded || isPinnedHeadsUp(child)), child);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002179 }
2180
2181
Selim Cinekcab4a602014-09-03 14:47:57 +02002182 private void updateAnimationState(boolean running, View child) {
2183 if (child instanceof ExpandableNotificationRow) {
2184 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2185 row.setIconAnimationRunning(running);
2186 }
Jorim Jaggi75c95042014-05-16 19:09:59 +02002187 }
2188
2189 public boolean isAddOrRemoveAnimationPending() {
2190 return mNeedsAnimation
2191 && (!mChildrenToAddAnimated.isEmpty() || !mChildrenToRemoveAnimated.isEmpty());
2192 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002193 /**
2194 * Generate an animation for an added child view.
2195 *
2196 * @param child The view to be added.
Jorim Jaggif6411742014-08-05 17:10:43 +00002197 * @param fromMoreCard Whether this add is coming from the "more" card on lockscreen.
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002198 */
Jorim Jaggif6411742014-08-05 17:10:43 +00002199 public void generateAddAnimation(View child, boolean fromMoreCard) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002200 if (mIsExpanded && mAnimationsEnabled && !mChangePositionInProgress) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002201 // Generate Animations
2202 mChildrenToAddAnimated.add(child);
Jorim Jaggif6411742014-08-05 17:10:43 +00002203 if (fromMoreCard) {
2204 mFromMoreCardAdditions.add(child);
2205 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002206 mNeedsAnimation = true;
Selim Cinek572bbd42014-04-25 16:43:27 +02002207 }
Adrian Roos777ef562015-12-01 17:37:14 -08002208 if (isHeadsUp(child) && !mChangePositionInProgress) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002209 mAddedHeadsUpChildren.add(child);
2210 mChildrenToAddAnimated.remove(child);
2211 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002212 }
2213
2214 /**
2215 * Change the position of child to a new location
2216 *
2217 * @param child the view to change the position for
2218 * @param newIndex the new index
2219 */
2220 public void changeViewPosition(View child, int newIndex) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002221 int currentIndex = indexOfChild(child);
2222 if (child != null && child.getParent() == this && currentIndex != newIndex) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002223 mChangePositionInProgress = true;
Selim Cinekc27437b2014-05-14 10:23:33 +02002224 removeView(child);
2225 addView(child, newIndex);
Selim Cinek159ffdb2014-06-04 22:24:18 +02002226 mChangePositionInProgress = false;
Dan Sandlereceda3d2014-07-21 15:35:01 -04002227 if (mIsExpanded && mAnimationsEnabled && child.getVisibility() != View.GONE) {
Selim Cinek159ffdb2014-06-04 22:24:18 +02002228 mChildrenChangingPositions.add(child);
2229 mNeedsAnimation = true;
2230 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002231 }
2232 }
2233
Selim Cinekf4c19962014-05-01 21:55:31 +02002234 private void startAnimationToState() {
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002235 if (mNeedsAnimation) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002236 generateChildHierarchyEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002237 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002238 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002239 if (!mAnimationEvents.isEmpty() || isCurrentlyAnimating()) {
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002240 mStateAnimator.startAnimationForEvents(mAnimationEvents, mCurrentStackScrollState,
2241 mGoToFullShadeDelay);
Selim Cinek614576e2016-01-20 10:54:09 -08002242 setAnimationRunning(true);
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002243 mAnimationEvents.clear();
Selim Cinek6811d722016-01-19 17:53:12 -08002244 updateBackground();
Selim Cinekf4c19962014-05-01 21:55:31 +02002245 } else {
2246 applyCurrentState();
2247 }
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002248 mGoToFullShadeDelay = 0;
Selim Cinek572bbd42014-04-25 16:43:27 +02002249 }
2250
2251 private void generateChildHierarchyEvents() {
Selim Cineka59ecc32015-04-07 10:51:49 -07002252 generateHeadsUpAnimationEvents();
Selim Cinek572bbd42014-04-25 16:43:27 +02002253 generateChildRemovalEvents();
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002254 generateChildAdditionEvents();
2255 generatePositionChangeEvents();
Selim Cinekeb973562014-05-02 17:07:49 +02002256 generateSnapBackEvents();
2257 generateDragEvents();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002258 generateTopPaddingEvent();
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002259 generateActivateEvent();
2260 generateDimmedEvent();
Jorim Jaggiae441282014-08-01 02:45:18 +02002261 generateHideSensitiveEvent();
John Spurlockbf370992014-06-17 13:58:31 -04002262 generateDarkEvent();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002263 generateGoToFullShadeEvent();
Selim Cineka5e211b2014-08-11 17:35:48 +02002264 generateViewResizeEvent();
Selim Cinekb5605e52015-02-20 18:21:41 +01002265 generateGroupExpansionEvent();
Selim Cinekd9acca52014-09-01 22:33:25 +02002266 generateAnimateEverythingEvent();
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002267 mNeedsAnimation = false;
Selim Cinek572bbd42014-04-25 16:43:27 +02002268 }
2269
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002270 private void generateHeadsUpAnimationEvents() {
2271 for (Pair<ExpandableNotificationRow, Boolean> eventPair : mHeadsUpChangeAnimations) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002272 ExpandableNotificationRow row = eventPair.first;
2273 boolean isHeadsUp = eventPair.second;
2274 int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
2275 boolean onBottom = false;
Selim Cinek131c1e22015-05-11 19:04:49 -07002276 boolean pinnedAndClosed = row.isPinned() && !mIsExpanded;
Selim Cinekaac93252015-04-14 20:04:12 -07002277 if (!mIsExpanded && !isHeadsUp) {
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07002278 type = row.wasJustClicked()
2279 ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
2280 : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
Selim Cinekeaee9c02015-06-25 11:04:20 -04002281 } else {
2282 StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
2283 if (viewState == null) {
2284 // A view state was never generated for this view, so we don't need to animate
2285 // this. This may happen with notification children.
2286 continue;
Selim Cineka59ecc32015-04-07 10:51:49 -07002287 }
Selim Cinekeaee9c02015-06-25 11:04:20 -04002288 if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
2289 if (pinnedAndClosed || shouldHunAppearFromBottom(viewState)) {
2290 // Our custom add animation
2291 type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR;
2292 } else {
2293 // Normal add animation
2294 type = AnimationEvent.ANIMATION_TYPE_ADD;
2295 }
2296 onBottom = !pinnedAndClosed;
2297 }
Selim Cineka59ecc32015-04-07 10:51:49 -07002298 }
2299 AnimationEvent event = new AnimationEvent(row, type);
2300 event.headsUpFromBottom = onBottom;
2301 mAnimationEvents.add(event);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002302 }
2303 mHeadsUpChangeAnimations.clear();
Selim Cineka59ecc32015-04-07 10:51:49 -07002304 mAddedHeadsUpChildren.clear();
2305 }
2306
Selim Cinekeaee9c02015-06-25 11:04:20 -04002307 private boolean shouldHunAppearFromBottom(StackViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -07002308 if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
2309 return false;
2310 }
2311 return true;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002312 }
2313
Selim Cinekb5605e52015-02-20 18:21:41 +01002314 private void generateGroupExpansionEvent() {
2315 // Generate a group expansion/collapsing event if there is such a group at all
2316 if (mExpandedGroupView != null) {
2317 mAnimationEvents.add(new AnimationEvent(mExpandedGroupView,
2318 AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED));
2319 mExpandedGroupView = null;
2320 }
2321 }
2322
Selim Cineka5e211b2014-08-11 17:35:48 +02002323 private void generateViewResizeEvent() {
2324 if (mNeedViewResizeAnimation) {
2325 mAnimationEvents.add(
2326 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_VIEW_RESIZE));
2327 }
2328 mNeedViewResizeAnimation = false;
2329 }
2330
Selim Cinekeb973562014-05-02 17:07:49 +02002331 private void generateSnapBackEvents() {
2332 for (View child : mSnappedBackChildren) {
2333 mAnimationEvents.add(new AnimationEvent(child,
2334 AnimationEvent.ANIMATION_TYPE_SNAP_BACK));
2335 }
2336 mSnappedBackChildren.clear();
2337 }
2338
2339 private void generateDragEvents() {
2340 for (View child : mDragAnimPendingChildren) {
2341 mAnimationEvents.add(new AnimationEvent(child,
2342 AnimationEvent.ANIMATION_TYPE_START_DRAG));
2343 }
2344 mDragAnimPendingChildren.clear();
2345 }
2346
Selim Cinek572bbd42014-04-25 16:43:27 +02002347 private void generateChildRemovalEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002348 for (View child : mChildrenToRemoveAnimated) {
Selim Cinek572bbd42014-04-25 16:43:27 +02002349 boolean childWasSwipedOut = mSwipedOutViews.contains(child);
2350 int animationType = childWasSwipedOut
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002351 ? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
2352 : AnimationEvent.ANIMATION_TYPE_REMOVE;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002353 AnimationEvent event = new AnimationEvent(child, animationType);
2354
2355 // we need to know the view after this one
2356 event.viewAfterChangingView = getFirstChildBelowTranlsationY(child.getTranslationY());
2357 mAnimationEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +02002358 }
2359 mSwipedOutViews.clear();
2360 mChildrenToRemoveAnimated.clear();
2361 }
2362
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002363 private void generatePositionChangeEvents() {
2364 for (View child : mChildrenChangingPositions) {
2365 mAnimationEvents.add(new AnimationEvent(child,
2366 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2367 }
2368 mChildrenChangingPositions.clear();
Selim Cinekb5605e52015-02-20 18:21:41 +01002369 if (mGenerateChildOrderChangedEvent) {
2370 mAnimationEvents.add(new AnimationEvent(null,
2371 AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
2372 mGenerateChildOrderChangedEvent = false;
2373 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +02002374 }
2375
Selim Cinek572bbd42014-04-25 16:43:27 +02002376 private void generateChildAdditionEvents() {
Selim Cinekeb973562014-05-02 17:07:49 +02002377 for (View child : mChildrenToAddAnimated) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002378 if (mFromMoreCardAdditions.contains(child)) {
2379 mAnimationEvents.add(new AnimationEvent(child,
2380 AnimationEvent.ANIMATION_TYPE_ADD,
2381 StackStateAnimator.ANIMATION_DURATION_STANDARD));
2382 } else {
2383 mAnimationEvents.add(new AnimationEvent(child,
2384 AnimationEvent.ANIMATION_TYPE_ADD));
2385 }
Selim Cinek572bbd42014-04-25 16:43:27 +02002386 }
2387 mChildrenToAddAnimated.clear();
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02002388 mFromMoreCardAdditions.clear();
Christoph Studer068f5922014-04-08 17:43:07 -04002389 }
2390
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002391 private void generateTopPaddingEvent() {
Jorim Jaggi98fb09c2014-05-01 22:40:56 +02002392 if (mTopPaddingNeedsAnimation) {
2393 mAnimationEvents.add(
2394 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_TOP_PADDING_CHANGED));
2395 }
Jorim Jaggi0dd68812014-05-01 19:17:37 +02002396 mTopPaddingNeedsAnimation = false;
2397 }
2398
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002399 private void generateActivateEvent() {
2400 if (mActivateNeedsAnimation) {
2401 mAnimationEvents.add(
2402 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
2403 }
2404 mActivateNeedsAnimation = false;
2405 }
2406
Selim Cinekd9acca52014-09-01 22:33:25 +02002407 private void generateAnimateEverythingEvent() {
2408 if (mEverythingNeedsAnimation) {
2409 mAnimationEvents.add(
2410 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_EVERYTHING));
2411 }
2412 mEverythingNeedsAnimation = false;
2413 }
2414
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002415 private void generateDimmedEvent() {
2416 if (mDimmedNeedsAnimation) {
2417 mAnimationEvents.add(
2418 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
2419 }
2420 mDimmedNeedsAnimation = false;
2421 }
2422
Jorim Jaggiae441282014-08-01 02:45:18 +02002423 private void generateHideSensitiveEvent() {
2424 if (mHideSensitiveNeedsAnimation) {
2425 mAnimationEvents.add(
2426 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_HIDE_SENSITIVE));
2427 }
2428 mHideSensitiveNeedsAnimation = false;
2429 }
2430
John Spurlockbf370992014-06-17 13:58:31 -04002431 private void generateDarkEvent() {
2432 if (mDarkNeedsAnimation) {
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002433 AnimationEvent ev = new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DARK);
2434 ev.darkAnimationOriginIndex = mDarkAnimationOriginIndex;
2435 mAnimationEvents.add(ev);
John Spurlockbf370992014-06-17 13:58:31 -04002436 }
2437 mDarkNeedsAnimation = false;
2438 }
2439
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002440 private void generateGoToFullShadeEvent() {
2441 if (mGoToFullShadeNeedsAnimation) {
2442 mAnimationEvents.add(
2443 new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE));
2444 }
2445 mGoToFullShadeNeedsAnimation = false;
2446 }
2447
Selim Cinek67b22602014-03-10 15:40:16 +01002448 private boolean onInterceptTouchEventScroll(MotionEvent ev) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002449 if (!isScrollingEnabled()) {
2450 return false;
2451 }
Selim Cinek67b22602014-03-10 15:40:16 +01002452 /*
2453 * This method JUST determines whether we want to intercept the motion.
2454 * If we return true, onMotionEvent will be called and we do the actual
2455 * scrolling there.
2456 */
2457
2458 /*
2459 * Shortcut the most recurring case: the user is in the dragging
Chris Wren5d53df42015-06-26 11:26:03 -04002460 * state and is moving their finger. We want to intercept this
Selim Cinek67b22602014-03-10 15:40:16 +01002461 * motion.
2462 */
2463 final int action = ev.getAction();
2464 if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
2465 return true;
2466 }
2467
Selim Cinek67b22602014-03-10 15:40:16 +01002468 switch (action & MotionEvent.ACTION_MASK) {
2469 case MotionEvent.ACTION_MOVE: {
2470 /*
2471 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
Chris Wren5d53df42015-06-26 11:26:03 -04002472 * whether the user has moved far enough from the original down touch.
Selim Cinek67b22602014-03-10 15:40:16 +01002473 */
2474
2475 /*
2476 * Locally do absolute value. mLastMotionY is set to the y value
2477 * of the down event.
2478 */
2479 final int activePointerId = mActivePointerId;
2480 if (activePointerId == INVALID_POINTER) {
2481 // If we don't have a valid id, the touch down wasn't on content.
2482 break;
2483 }
2484
2485 final int pointerIndex = ev.findPointerIndex(activePointerId);
2486 if (pointerIndex == -1) {
2487 Log.e(TAG, "Invalid pointerId=" + activePointerId
2488 + " in onInterceptTouchEvent");
2489 break;
2490 }
2491
2492 final int y = (int) ev.getY(pointerIndex);
Selim Cinek1408eb52014-06-02 14:45:38 +02002493 final int x = (int) ev.getX(pointerIndex);
Selim Cinek67b22602014-03-10 15:40:16 +01002494 final int yDiff = Math.abs(y - mLastMotionY);
Selim Cinek1408eb52014-06-02 14:45:38 +02002495 final int xDiff = Math.abs(x - mDownX);
2496 if (yDiff > mTouchSlop && yDiff > xDiff) {
Selim Cinek67b22602014-03-10 15:40:16 +01002497 setIsBeingDragged(true);
2498 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002499 mDownX = x;
Selim Cinek67b22602014-03-10 15:40:16 +01002500 initVelocityTrackerIfNotExists();
2501 mVelocityTracker.addMovement(ev);
Selim Cinek67b22602014-03-10 15:40:16 +01002502 }
2503 break;
2504 }
2505
2506 case MotionEvent.ACTION_DOWN: {
2507 final int y = (int) ev.getY();
Jayasri bhattacharyya5e55c892015-09-10 16:00:10 +05302508 mScrolledToTopOnFirstDown = isScrolledToTop();
Selim Cinek67b22602014-03-10 15:40:16 +01002509 if (getChildAtPosition(ev.getX(), y) == null) {
2510 setIsBeingDragged(false);
2511 recycleVelocityTracker();
2512 break;
2513 }
2514
2515 /*
2516 * Remember location of down touch.
2517 * ACTION_DOWN always refers to pointer index 0.
2518 */
2519 mLastMotionY = y;
Selim Cinek1408eb52014-06-02 14:45:38 +02002520 mDownX = (int) ev.getX();
Selim Cinek67b22602014-03-10 15:40:16 +01002521 mActivePointerId = ev.getPointerId(0);
2522
2523 initOrResetVelocityTracker();
2524 mVelocityTracker.addMovement(ev);
2525 /*
2526 * If being flinged and user touches the screen, initiate drag;
2527 * otherwise don't. mScroller.isFinished should be false when
2528 * being flinged.
2529 */
2530 boolean isBeingDragged = !mScroller.isFinished();
2531 setIsBeingDragged(isBeingDragged);
2532 break;
2533 }
2534
2535 case MotionEvent.ACTION_CANCEL:
2536 case MotionEvent.ACTION_UP:
2537 /* Release the drag */
2538 setIsBeingDragged(false);
2539 mActivePointerId = INVALID_POINTER;
2540 recycleVelocityTracker();
2541 if (mScroller.springBack(mScrollX, mOwnScrollY, 0, 0, 0, getScrollRange())) {
2542 postInvalidateOnAnimation();
2543 }
2544 break;
2545 case MotionEvent.ACTION_POINTER_UP:
2546 onSecondaryPointerUp(ev);
2547 break;
2548 }
2549
2550 /*
2551 * The only time we want to intercept motion events is if we are in the
2552 * drag mode.
2553 */
2554 return mIsBeingDragged;
2555 }
2556
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002557 /**
2558 * @return Whether the specified motion event is actually happening over the content.
2559 */
2560 private boolean isInContentBounds(MotionEvent event) {
Selim Cinekab1dc952014-10-30 20:20:29 +01002561 return isInContentBounds(event.getY());
2562 }
2563
2564 /**
2565 * @return Whether a y coordinate is inside the content.
2566 */
2567 public boolean isInContentBounds(float y) {
2568 return y < getHeight() - getEmptyBottomMargin();
Jorim Jaggife6bfa62014-05-07 23:23:18 +02002569 }
2570
Selim Cinek67b22602014-03-10 15:40:16 +01002571 private void setIsBeingDragged(boolean isDragged) {
2572 mIsBeingDragged = isDragged;
2573 if (isDragged) {
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002574 requestDisallowInterceptTouchEvent(true);
Selim Cinek1408eb52014-06-02 14:45:38 +02002575 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002576 }
2577 }
2578
2579 @Override
2580 public void onWindowFocusChanged(boolean hasWindowFocus) {
2581 super.onWindowFocusChanged(hasWindowFocus);
2582 if (!hasWindowFocus) {
Selim Cinek1408eb52014-06-02 14:45:38 +02002583 removeLongPressCallback();
Selim Cinek67b22602014-03-10 15:40:16 +01002584 }
2585 }
Selim Cinekfab078b2014-03-27 22:45:58 +01002586
Selim Cinek1408eb52014-06-02 14:45:38 +02002587 public void removeLongPressCallback() {
2588 mSwipeHelper.removeLongPressCallback();
2589 }
2590
Selim Cinekfab078b2014-03-27 22:45:58 +01002591 @Override
2592 public boolean isScrolledToTop() {
2593 return mOwnScrollY == 0;
2594 }
2595
2596 @Override
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002597 public boolean isScrolledToBottom() {
2598 return mOwnScrollY >= getScrollRange();
2599 }
2600
2601 @Override
Selim Cinekfab078b2014-03-27 22:45:58 +01002602 public View getHostView() {
2603 return this;
2604 }
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002605
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002606 public int getEmptyBottomMargin() {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002607 int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002608 if (needsHeightAdaption()) {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002609 emptyMargin -= mBottomStackSlowDownHeight;
Jorim Jaggi1d480692014-05-20 19:41:58 +02002610 } else {
Jorim Jaggi2c5baca2014-07-11 01:30:10 +02002611 emptyMargin -= mCollapseSecondCardPadding;
Selim Cinek4a1ac842014-05-01 15:51:58 +02002612 }
2613 return Math.max(emptyMargin, 0);
Selim Cinekb6d85eb2014-03-28 20:21:01 +01002614 }
2615
Selim Cinek5f71bee2015-11-18 10:25:23 -08002616 public float getKeyguardBottomStackSize() {
2617 return mBottomStackPeekSize + getResources().getDimensionPixelSize(
2618 R.dimen.bottom_stack_slow_down_length);
2619 }
2620
Selim Cinek1685e632014-04-08 02:27:49 +02002621 public void onExpansionStarted() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002622 mIsExpansionChanging = true;
Selim Cinek1685e632014-04-08 02:27:49 +02002623 mStackScrollAlgorithm.onExpansionStarted(mCurrentStackScrollState);
2624 }
2625
2626 public void onExpansionStopped() {
Selim Cinekc27437b2014-05-14 10:23:33 +02002627 mIsExpansionChanging = false;
Selim Cinek1685e632014-04-08 02:27:49 +02002628 mStackScrollAlgorithm.onExpansionStopped();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002629 if (!mIsExpanded) {
2630 mOwnScrollY = 0;
Selim Cinekf336f4c2014-11-12 16:58:16 +01002631
2632 // lets make sure nothing is in the overlay anymore
2633 getOverlay().clear();
Selim Cinek4fe3e472014-07-03 16:32:54 +02002634 }
Selim Cinek1685e632014-04-08 02:27:49 +02002635 }
2636
Jorim Jaggie4b840d2015-06-30 16:19:17 -07002637 public void onPanelTrackingStarted() {
2638 mPanelTracking = true;
2639 }
2640 public void onPanelTrackingStopped() {
2641 mPanelTracking = false;
2642 }
2643
Selim Cinekb24e0a92015-06-09 20:17:30 -07002644 public void resetScrollPosition() {
2645 mScroller.abortAnimation();
2646 mOwnScrollY = 0;
2647 }
2648
Jorim Jaggi8c1a44b2014-04-29 19:04:02 +02002649 private void setIsExpanded(boolean isExpanded) {
Selim Cinekcab4a602014-09-03 14:47:57 +02002650 boolean changed = isExpanded != mIsExpanded;
Selim Cinek572bbd42014-04-25 16:43:27 +02002651 mIsExpanded = isExpanded;
Selim Cinek1685e632014-04-08 02:27:49 +02002652 mStackScrollAlgorithm.setIsExpanded(isExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002653 if (changed) {
Selim Cinek9184f9c2016-02-02 17:36:53 -08002654 if (!mIsExpanded) {
2655 mGroupManager.collapseAllGroups();
2656 }
Selim Cinekcab4a602014-09-03 14:47:57 +02002657 updateNotificationAnimationStates();
Selim Cinek98713a42015-09-21 15:47:20 +02002658 updateChronometers();
2659 }
2660 }
2661
2662 private void updateChronometers() {
2663 int childCount = getChildCount();
2664 for (int i = 0; i < childCount; i++) {
2665 updateChronometerForChild(getChildAt(i));
2666 }
2667 }
2668
2669 private void updateChronometerForChild(View child) {
2670 if (child instanceof ExpandableNotificationRow) {
2671 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
2672 row.setChronometerRunning(mIsExpanded);
Selim Cinekcab4a602014-09-03 14:47:57 +02002673 }
Selim Cinek1685e632014-04-08 02:27:49 +02002674 }
2675
Jorim Jaggibe565df2014-04-28 17:51:23 +02002676 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01002677 public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002678 updateContentHeight();
Selim Cinekf7a14c02014-07-07 14:01:46 +02002679 updateScrollPositionOnExpandInBottom(view);
2680 clampScrollPosition();
Selim Cinekaef92ef2014-06-06 18:06:04 +02002681 notifyHeightChangeListener(view);
Selim Cinekb5605e52015-02-20 18:21:41 +01002682 if (needsAnimation) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08002683 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
2684 ? (ExpandableNotificationRow) view
2685 : null;
2686 requestAnimationOnViewResize(row);
Selim Cinekb5605e52015-02-20 18:21:41 +01002687 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002688 requestChildrenUpdate();
Jorim Jaggibe565df2014-04-28 17:51:23 +02002689 }
2690
Selim Cineka5e211b2014-08-11 17:35:48 +02002691 @Override
2692 public void onReset(ExpandableView view) {
Selim Cinek0e41dea2014-08-25 13:55:06 +02002693 if (mIsExpanded && mAnimationsEnabled) {
2694 mRequestViewResizeAnimationOnLayout = true;
2695 }
Selim Cinek31094df2014-08-14 19:28:15 +02002696 mStackScrollAlgorithm.onReset(view);
Selim Cinek51ae05d2014-09-09 15:51:38 +02002697 updateAnimationState(view);
Selim Cinek98713a42015-09-21 15:47:20 +02002698 updateChronometerForChild(view);
Selim Cineka5e211b2014-08-11 17:35:48 +02002699 }
2700
Selim Cinekf7a14c02014-07-07 14:01:46 +02002701 private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
2702 if (view instanceof ExpandableNotificationRow) {
2703 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
Selim Cinekcb9400a2015-06-03 16:56:13 +02002704 if (row.isUserLocked() && row != getFirstChildNotGone()) {
Selim Cinekf7a14c02014-07-07 14:01:46 +02002705 // We are actually expanding this view
Selim Cinek388df6d2015-10-22 13:25:11 -07002706 float endPosition;
2707 if (row.isChildInGroup()) {
2708 ExpandableNotificationRow parent = row.getNotificationParent();
2709 endPosition = parent.getTranslationY() + parent.getActualHeight();
2710 } else {
2711 endPosition = row.getTranslationY() + row.getActualHeight();
2712 }
Selim Cinekf7a14c02014-07-07 14:01:46 +02002713 int stackEnd = mMaxLayoutHeight - mBottomStackPeekSize -
Selim Cinekcb9400a2015-06-03 16:56:13 +02002714 mBottomStackSlowDownHeight + (int) mStackTranslation;
Selim Cinekf7a14c02014-07-07 14:01:46 +02002715 if (endPosition > stackEnd) {
2716 mOwnScrollY += endPosition - stackEnd;
2717 mDisallowScrollingInThisMotion = true;
2718 }
2719 }
2720 }
2721 }
2722
Jorim Jaggibe565df2014-04-28 17:51:23 +02002723 public void setOnHeightChangedListener(
2724 ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
2725 this.mOnHeightChangedListener = mOnHeightChangedListener;
2726 }
2727
Selim Cinek3a9c10a2014-10-28 14:21:10 +01002728 public void setOnEmptySpaceClickListener(OnEmptySpaceClickListener listener) {
2729 mOnEmptySpaceClickListener = listener;
2730 }
2731
Selim Cinek572bbd42014-04-25 16:43:27 +02002732 public void onChildAnimationFinished() {
Selim Cinek6811d722016-01-19 17:53:12 -08002733 setAnimationRunning(false);
Selim Cinek319bdc42014-05-01 23:01:58 +02002734 requestChildrenUpdate();
Selim Cinek32a59fd32015-06-10 13:54:42 -07002735 runAnimationFinishedRunnables();
Selim Cinek0fccc722015-07-29 17:04:36 -07002736 clearViewOverlays();
2737 }
2738
2739 private void clearViewOverlays() {
2740 for (View view : mClearOverlayViewsWhenFinished) {
2741 getOverlay().remove(view);
2742 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002743 }
2744
2745 private void runAnimationFinishedRunnables() {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07002746 for (Runnable runnable : mAnimationFinishedRunnables) {
2747 runnable.run();
2748 }
2749 mAnimationFinishedRunnables.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +02002750 }
2751
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002752 /**
2753 * See {@link AmbientState#setDimmed}.
2754 */
2755 public void setDimmed(boolean dimmed, boolean animate) {
2756 mAmbientState.setDimmed(dimmed);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002757 if (animate && mAnimationsEnabled) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002758 mDimmedNeedsAnimation = true;
2759 mNeedsAnimation = true;
Selim Cinekd35c2792016-01-21 13:20:57 -08002760 animateDimmed(dimmed);
2761 } else {
2762 setDimAmount(dimmed ? 1.0f : 0.0f);
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002763 }
2764 requestChildrenUpdate();
2765 }
2766
Selim Cinekd35c2792016-01-21 13:20:57 -08002767 private void setDimAmount(float dimAmount) {
2768 mDimAmount = dimAmount;
2769 updateBackgroundDimming();
2770 }
2771
2772 private void animateDimmed(boolean dimmed) {
2773 if (mDimAnimator != null) {
2774 mDimAnimator.cancel();
2775 }
2776 float target = dimmed ? 1.0f : 0.0f;
2777 if (target == mDimAmount) {
2778 return;
2779 }
2780 mDimAnimator = TimeAnimator.ofFloat(mDimAmount, target);
2781 mDimAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED);
2782 mDimAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
2783 mDimAnimator.addListener(mDimEndListener);
2784 mDimAnimator.addUpdateListener(mDimUpdateListener);
2785 mDimAnimator.start();
2786 }
2787
Jorim Jaggiae441282014-08-01 02:45:18 +02002788 public void setHideSensitive(boolean hideSensitive, boolean animate) {
2789 if (hideSensitive != mAmbientState.isHideSensitive()) {
2790 int childCount = getChildCount();
2791 for (int i = 0; i < childCount; i++) {
2792 ExpandableView v = (ExpandableView) getChildAt(i);
2793 v.setHideSensitiveForIntrinsicHeight(hideSensitive);
2794 }
2795 mAmbientState.setHideSensitive(hideSensitive);
2796 if (animate && mAnimationsEnabled) {
2797 mHideSensitiveNeedsAnimation = true;
2798 mNeedsAnimation = true;
2799 }
2800 requestChildrenUpdate();
2801 }
2802 }
2803
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002804 /**
2805 * See {@link AmbientState#setActivatedChild}.
2806 */
Selim Cineka32ab602014-06-11 15:06:01 +02002807 public void setActivatedChild(ActivatableNotificationView activatedChild) {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002808 mAmbientState.setActivatedChild(activatedChild);
Jorim Jaggi75c95042014-05-16 19:09:59 +02002809 if (mAnimationsEnabled) {
2810 mActivateNeedsAnimation = true;
2811 mNeedsAnimation = true;
2812 }
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002813 requestChildrenUpdate();
2814 }
2815
Selim Cineka32ab602014-06-11 15:06:01 +02002816 public ActivatableNotificationView getActivatedChild() {
Jorim Jaggid552d9d2014-05-07 19:41:13 +02002817 return mAmbientState.getActivatedChild();
2818 }
2819
Selim Cinek572bbd42014-04-25 16:43:27 +02002820 private void applyCurrentState() {
Selim Cinek572bbd42014-04-25 16:43:27 +02002821 mCurrentStackScrollState.apply();
Selim Cinekf4c19962014-05-01 21:55:31 +02002822 if (mListener != null) {
2823 mListener.onChildLocationsChanged(this);
2824 }
Selim Cinek32a59fd32015-06-10 13:54:42 -07002825 runAnimationFinishedRunnables();
Selim Cinek6811d722016-01-19 17:53:12 -08002826 updateBackground();
Selim Cinek572bbd42014-04-25 16:43:27 +02002827 }
2828
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002829 public void goToFullShade(long delay) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04002830 mDismissView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002831 mEmptyShadeView.setInvisible();
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002832 mGoToFullShadeNeedsAnimation = true;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02002833 mGoToFullShadeDelay = delay;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002834 mNeedsAnimation = true;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02002835 requestChildrenUpdate();
Selim Cinekc27437b2014-05-14 10:23:33 +02002836 }
2837
Selim Cinek1408eb52014-06-02 14:45:38 +02002838 public void cancelExpandHelper() {
2839 mExpandHelper.cancel();
2840 }
2841
2842 public void setIntrinsicPadding(int intrinsicPadding) {
2843 mIntrinsicPadding = intrinsicPadding;
2844 }
2845
Jorim Jaggi30c305c2014-07-01 23:34:41 +02002846 public int getIntrinsicPadding() {
2847 return mIntrinsicPadding;
2848 }
2849
Christoph Studer6e3eceb2014-04-01 18:40:27 +02002850 /**
Jorim Jaggi457cc352014-06-02 22:47:42 +02002851 * @return the y position of the first notification
2852 */
2853 public float getNotificationsTopY() {
Selim Cinekd2281152015-04-10 14:37:46 -07002854 return mTopPadding + getStackTranslation();
Jorim Jaggi457cc352014-06-02 22:47:42 +02002855 }
2856
Selim Cinekc0ce82d2014-06-10 13:21:15 +02002857 @Override
2858 public boolean shouldDelayChildPressedState() {
2859 return true;
2860 }
2861
Jorim Jaggi457cc352014-06-02 22:47:42 +02002862 /**
John Spurlockbf370992014-06-17 13:58:31 -04002863 * See {@link AmbientState#setDark}.
2864 */
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002865 public void setDark(boolean dark, boolean animate, @Nullable PointF touchWakeUpScreenLocation) {
John Spurlockbf370992014-06-17 13:58:31 -04002866 mAmbientState.setDark(dark);
2867 if (animate && mAnimationsEnabled) {
2868 mDarkNeedsAnimation = true;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002869 mDarkAnimationOriginIndex = findDarkAnimationOriginIndex(touchWakeUpScreenLocation);
John Spurlockbf370992014-06-17 13:58:31 -04002870 mNeedsAnimation = true;
2871 }
2872 requestChildrenUpdate();
Selim Cinek6811d722016-01-19 17:53:12 -08002873 if (dark) {
2874 setWillNotDraw(!DEBUG);
2875 mScrimController.setExcludedBackgroundArea(null);
2876 } else {
2877 updateBackground();
2878 setWillNotDraw(false);
2879 // TODO: fade in background
2880 }
John Spurlockbf370992014-06-17 13:58:31 -04002881 }
2882
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01002883 private int findDarkAnimationOriginIndex(@Nullable PointF screenLocation) {
2884 if (screenLocation == null || screenLocation.y < mTopPadding + mTopPaddingOverflow) {
2885 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2886 }
2887 if (screenLocation.y > getBottomMostNotificationBottom()) {
2888 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_BELOW;
2889 }
2890 View child = getClosestChildAtRawPosition(screenLocation.x, screenLocation.y);
2891 if (child != null) {
2892 return getNotGoneIndex(child);
2893 } else {
2894 return AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE;
2895 }
2896 }
2897
2898 private int getNotGoneIndex(View child) {
2899 int count = getChildCount();
2900 int notGoneIndex = 0;
2901 for (int i = 0; i < count; i++) {
2902 View v = getChildAt(i);
2903 if (child == v) {
2904 return notGoneIndex;
2905 }
2906 if (v.getVisibility() != View.GONE) {
2907 notGoneIndex++;
2908 }
2909 }
2910 return -1;
2911 }
2912
Dan Sandlereceda3d2014-07-21 15:35:01 -04002913 public void setDismissView(DismissView dismissView) {
Selim Cinek01af3342016-02-09 19:25:31 -08002914 int index = -1;
2915 if (mDismissView != null) {
2916 index = indexOfChild(mDismissView);
2917 removeView(mDismissView);
2918 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04002919 mDismissView = dismissView;
Selim Cinek01af3342016-02-09 19:25:31 -08002920 addView(mDismissView, index);
Dan Sandlereceda3d2014-07-21 15:35:01 -04002921 }
2922
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002923 public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
Selim Cinek01af3342016-02-09 19:25:31 -08002924 int index = -1;
2925 if (mEmptyShadeView != null) {
2926 index = indexOfChild(mEmptyShadeView);
2927 removeView(mEmptyShadeView);
2928 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002929 mEmptyShadeView = emptyShadeView;
Selim Cinek01af3342016-02-09 19:25:31 -08002930 addView(mEmptyShadeView, index);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002931 }
2932
2933 public void updateEmptyShadeView(boolean visible) {
2934 int oldVisibility = mEmptyShadeView.willBeGone() ? GONE : mEmptyShadeView.getVisibility();
2935 int newVisibility = visible ? VISIBLE : GONE;
2936 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02002937 if (newVisibility != GONE) {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002938 if (mEmptyShadeView.willBeGone()) {
2939 mEmptyShadeView.cancelAnimation();
2940 } else {
2941 mEmptyShadeView.setInvisible();
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002942 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02002943 mEmptyShadeView.setVisibility(newVisibility);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002944 mEmptyShadeView.setWillBeGone(false);
2945 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07002946 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002947 } else {
Selim Cinek20867102014-12-10 17:09:17 +01002948 Runnable onFinishedRunnable = new Runnable() {
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002949 @Override
2950 public void run() {
2951 mEmptyShadeView.setVisibility(GONE);
2952 mEmptyShadeView.setWillBeGone(false);
2953 updateContentHeight();
Selim Cinek2cd45df2015-06-09 18:00:07 -07002954 notifyHeightChangeListener(mEmptyShadeView);
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002955 }
Selim Cinek20867102014-12-10 17:09:17 +01002956 };
2957 if (mAnimationsEnabled) {
2958 mEmptyShadeView.setWillBeGone(true);
2959 mEmptyShadeView.performVisibilityAnimation(false, onFinishedRunnable);
2960 } else {
2961 mEmptyShadeView.setInvisible();
2962 onFinishedRunnable.run();
2963 }
Jorim Jaggia2052ea2014-08-05 16:22:30 +02002964 }
2965 }
2966 }
2967
Selim Cinek2cd45df2015-06-09 18:00:07 -07002968 public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
2969 mOverflowContainer = overFlowContainer;
2970 addView(mOverflowContainer);
2971 }
2972
2973 public void updateOverflowContainerVisibility(boolean visible) {
2974 int oldVisibility = mOverflowContainer.willBeGone() ? GONE
2975 : mOverflowContainer.getVisibility();
2976 final int newVisibility = visible ? VISIBLE : GONE;
2977 if (oldVisibility != newVisibility) {
2978 Runnable onFinishedRunnable = new Runnable() {
2979 @Override
2980 public void run() {
2981 mOverflowContainer.setVisibility(newVisibility);
2982 mOverflowContainer.setWillBeGone(false);
2983 updateContentHeight();
2984 notifyHeightChangeListener(mOverflowContainer);
2985 }
2986 };
2987 if (!mAnimationsEnabled || !mIsExpanded) {
2988 mOverflowContainer.cancelAppearDrawing();
2989 onFinishedRunnable.run();
2990 } else if (newVisibility != GONE) {
2991 mOverflowContainer.performAddAnimation(0,
2992 StackStateAnimator.ANIMATION_DURATION_STANDARD);
2993 mOverflowContainer.setVisibility(newVisibility);
2994 mOverflowContainer.setWillBeGone(false);
2995 updateContentHeight();
2996 notifyHeightChangeListener(mOverflowContainer);
2997 } else {
2998 mOverflowContainer.performRemoveAnimation(
2999 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3000 0.0f,
3001 onFinishedRunnable);
3002 mOverflowContainer.setWillBeGone(true);
3003 }
3004 }
3005 }
3006
Dan Sandlereceda3d2014-07-21 15:35:01 -04003007 public void updateDismissView(boolean visible) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003008 int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003009 int newVisibility = visible ? VISIBLE : GONE;
3010 if (oldVisibility != newVisibility) {
Selim Cinekd2319fb2014-09-01 19:41:54 +02003011 if (newVisibility != GONE) {
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003012 if (mDismissView.willBeGone()) {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003013 mDismissView.cancelAnimation();
3014 } else {
3015 mDismissView.setInvisible();
Dan Sandlereceda3d2014-07-21 15:35:01 -04003016 }
Selim Cinekd2319fb2014-09-01 19:41:54 +02003017 mDismissView.setVisibility(newVisibility);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003018 mDismissView.setWillBeGone(false);
3019 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003020 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003021 } else {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003022 Runnable dimissHideFinishRunnable = new Runnable() {
Dan Sandlereceda3d2014-07-21 15:35:01 -04003023 @Override
3024 public void run() {
3025 mDismissView.setVisibility(GONE);
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003026 mDismissView.setWillBeGone(false);
3027 updateContentHeight();
Selim Cinekd2319fb2014-09-01 19:41:54 +02003028 notifyHeightChangeListener(mDismissView);
Dan Sandlereceda3d2014-07-21 15:35:01 -04003029 }
Selim Cinek7d5f3742014-11-07 18:07:49 +01003030 };
Selim Cinek20867102014-12-10 17:09:17 +01003031 if (mDismissView.isButtonVisible() && mIsExpanded && mAnimationsEnabled) {
Selim Cinek7d5f3742014-11-07 18:07:49 +01003032 mDismissView.setWillBeGone(true);
3033 mDismissView.performVisibilityAnimation(false, dimissHideFinishRunnable);
3034 } else {
3035 dimissHideFinishRunnable.run();
Selim Cinek7d5f3742014-11-07 18:07:49 +01003036 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003037 }
3038 }
3039 }
3040
3041 public void setDismissAllInProgress(boolean dismissAllInProgress) {
3042 mDismissAllInProgress = dismissAllInProgress;
Selim Cinek9c17b772015-07-07 20:37:09 -07003043 mAmbientState.setDismissAllInProgress(dismissAllInProgress);
Selim Cineka272dfe2015-02-20 18:12:28 +01003044 if (dismissAllInProgress) {
3045 disableClipOptimization();
3046 }
Selim Cinek9c17b772015-07-07 20:37:09 -07003047 handleDismissAllClipping();
Mady Mellor4b80b102016-01-22 08:03:58 -08003048 if (mCurrIconRow != null & mCurrIconRow.isVisible()) {
3049 mCurrIconRow.getNotificationParent().animateTranslateNotification(0 /* left target */);
3050 }
Selim Cinek9c17b772015-07-07 20:37:09 -07003051 }
3052
3053 private void handleDismissAllClipping() {
3054 final int count = getChildCount();
3055 boolean previousChildWillBeDismissed = false;
3056 for (int i = 0; i < count; i++) {
3057 ExpandableView child = (ExpandableView) getChildAt(i);
3058 if (child.getVisibility() == GONE) {
3059 continue;
3060 }
3061 if (mDismissAllInProgress && previousChildWillBeDismissed) {
3062 child.setMinClipTopAmount(child.getClipTopAmount());
3063 } else {
3064 child.setMinClipTopAmount(0);
3065 }
3066 previousChildWillBeDismissed = canChildBeDismissed(child);
3067 }
Selim Cineka272dfe2015-02-20 18:12:28 +01003068 }
3069
3070 private void disableClipOptimization() {
3071 final int count = getChildCount();
3072 for (int i = 0; i < count; i++) {
3073 ExpandableView child = (ExpandableView) getChildAt(i);
3074 if (child.getVisibility() == GONE) {
3075 continue;
3076 }
3077 child.setClipTopOptimization(0);
3078 }
Dan Sandlereceda3d2014-07-21 15:35:01 -04003079 }
3080
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003081 public boolean isDismissViewNotGone() {
3082 return mDismissView.getVisibility() != View.GONE && !mDismissView.willBeGone();
3083 }
3084
3085 public boolean isDismissViewVisible() {
3086 return mDismissView.isVisible();
3087 }
3088
3089 public int getDismissViewHeight() {
Selim Cinek61633a82016-01-25 15:54:10 -08003090 int height = mDismissView.getHeight() + mPaddingBetweenElements;
Jorim Jaggi1d49ec92014-08-25 18:44:01 +02003091
3092 // Hack: Accommodate for additional distance when we only have one notification and the
3093 // dismiss all button.
3094 if (getNotGoneChildCount() == 2 && getLastChildNotGone() == mDismissView
3095 && getFirstChildNotGone() instanceof ActivatableNotificationView) {
3096 height += mCollapseSecondCardPadding;
3097 }
3098 return height;
Jorim Jaggi4b04a3a2014-07-28 17:43:56 +02003099 }
3100
Jorim Jaggi0cce70c2014-11-04 16:13:41 +01003101 public int getEmptyShadeViewHeight() {
3102 return mEmptyShadeView.getHeight();
3103 }
3104
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003105 public float getBottomMostNotificationBottom() {
3106 final int count = getChildCount();
3107 float max = 0;
3108 for (int childIdx = 0; childIdx < count; childIdx++) {
3109 ExpandableView child = (ExpandableView) getChildAt(childIdx);
3110 if (child.getVisibility() == GONE) {
3111 continue;
3112 }
3113 float bottom = child.getTranslationY() + child.getActualHeight();
3114 if (bottom > max) {
3115 max = bottom;
3116 }
3117 }
Selim Cinekd2281152015-04-10 14:37:46 -07003118 return max + getStackTranslation();
Jorim Jaggie0640dd2014-08-05 23:12:40 +02003119 }
3120
Selim Cinek19c8c702014-08-25 22:09:19 +02003121 public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
3122 this.mPhoneStatusBar = phoneStatusBar;
3123 }
3124
Selim Cinekb5605e52015-02-20 18:21:41 +01003125 public void setGroupManager(NotificationGroupManager groupManager) {
3126 this.mGroupManager = groupManager;
3127 }
3128
Selim Cinekd9acca52014-09-01 22:33:25 +02003129 public void onGoToKeyguard() {
Selim Cinek379ff8f2015-02-20 17:03:16 +01003130 requestAnimateEverything();
3131 }
3132
3133 private void requestAnimateEverything() {
Selim Cinekd9acca52014-09-01 22:33:25 +02003134 if (mIsExpanded && mAnimationsEnabled) {
3135 mEverythingNeedsAnimation = true;
Selim Cinek379ff8f2015-02-20 17:03:16 +01003136 mNeedsAnimation = true;
Selim Cinekd9acca52014-09-01 22:33:25 +02003137 requestChildrenUpdate();
3138 }
3139 }
3140
Selim Cinek04fb2582015-06-02 19:58:09 +02003141 public boolean isBelowLastNotification(float touchX, float touchY) {
Selim Cinekabf60bb2015-02-20 17:36:10 +01003142 int childCount = getChildCount();
3143 for (int i = childCount - 1; i >= 0; i--) {
3144 ExpandableView child = (ExpandableView) getChildAt(i);
3145 if (child.getVisibility() != View.GONE) {
3146 float childTop = child.getY();
3147 if (childTop > touchY) {
3148 // we are above a notification entirely let's abort
3149 return false;
3150 }
3151 boolean belowChild = touchY > childTop + child.getActualHeight();
3152 if (child == mDismissView) {
3153 if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
3154 touchY - childTop)) {
3155 // We clicked on the dismiss button
3156 return false;
3157 }
3158 } else if (child == mEmptyShadeView) {
3159 // We arrived at the empty shade view, for which we accept all clicks
3160 return true;
3161 } else if (!belowChild){
3162 // We are on a child
3163 return false;
3164 }
3165 }
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003166 }
Selim Cinek04fb2582015-06-02 19:58:09 +02003167 return touchY > mTopPadding + mStackTranslation;
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003168 }
3169
Selim Cinekb5605e52015-02-20 18:21:41 +01003170 @Override
3171 public void onGroupExpansionChanged(ExpandableNotificationRow changedRow, boolean expanded) {
Selim Cinek5bc852a2015-12-21 12:19:09 -08003172 boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
Selim Cinekb5605e52015-02-20 18:21:41 +01003173 if (animated) {
3174 mExpandedGroupView = changedRow;
3175 mNeedsAnimation = true;
3176 }
3177 changedRow.setChildrenExpanded(expanded, animated);
3178 onHeightChanged(changedRow, false /* needsAnimation */);
3179 }
3180
3181 @Override
Selim Cinekb5605e52015-02-20 18:21:41 +01003182 public void onGroupCreatedFromChildren(NotificationGroupManager.NotificationGroup group) {
Selim Cinekef5127e2015-12-21 16:55:58 -08003183 mPhoneStatusBar.requestNotificationUpdate();
3184 }
3185
3186 @Override
3187 public void onChildIsolationChanged() {
3188 mPhoneStatusBar.requestNotificationUpdate();
Selim Cinekb5605e52015-02-20 18:21:41 +01003189 }
3190
3191 public void generateChildOrderChangedEvent() {
3192 if (mIsExpanded && mAnimationsEnabled) {
3193 mGenerateChildOrderChangedEvent = true;
3194 mNeedsAnimation = true;
3195 requestChildrenUpdate();
3196 }
3197 }
3198
Selim Cinek684a4422015-04-15 16:18:39 -07003199 public void runAfterAnimationFinished(Runnable runnable) {
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003200 mAnimationFinishedRunnables.add(runnable);
3201 }
3202
3203 public void setHeadsUpManager(HeadsUpManager headsUpManager) {
3204 mHeadsUpManager = headsUpManager;
3205 mAmbientState.setHeadsUpManager(headsUpManager);
3206 }
3207
3208 public void generateHeadsUpAnimation(ExpandableNotificationRow row, boolean isHeadsUp) {
3209 if (mAnimationsEnabled) {
3210 mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
3211 mNeedsAnimation = true;
3212 requestChildrenUpdate();
3213 }
3214 }
3215
3216 public void setShadeExpanded(boolean shadeExpanded) {
3217 mAmbientState.setShadeExpanded(shadeExpanded);
Selim Cineka59ecc32015-04-07 10:51:49 -07003218 mStateAnimator.setShadeExpanded(shadeExpanded);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003219 }
3220
Selim Cineka59ecc32015-04-07 10:51:49 -07003221 /**
3222 * Set the boundary for the bottom heads up position. The heads up will always be above this
3223 * position.
3224 *
3225 * @param height the height of the screen
3226 * @param bottomBarHeight the height of the bar on the bottom
3227 */
3228 public void setHeadsUpBoundaries(int height, int bottomBarHeight) {
3229 mAmbientState.setMaxHeadsUpTranslation(height - bottomBarHeight);
3230 mStateAnimator.setHeadsUpAppearHeightBottom(height);
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003231 requestChildrenUpdate();
3232 }
3233
3234 public void setTrackingHeadsUp(boolean trackingHeadsUp) {
3235 mTrackingHeadsUp = trackingHeadsUp;
3236 }
3237
Selim Cinekaac93252015-04-14 20:04:12 -07003238 public void setScrimController(ScrimController scrimController) {
3239 mScrimController = scrimController;
Selim Cinekd35c2792016-01-21 13:20:57 -08003240 mScrimController.setScrimBehindChangeRunnable(new Runnable() {
3241 @Override
3242 public void run() {
3243 updateBackgroundDimming();
3244 }
3245 });
Selim Cinekaac93252015-04-14 20:04:12 -07003246 }
3247
Selim Cinekbbc580b2015-06-03 14:11:03 +02003248 public void forceNoOverlappingRendering(boolean force) {
3249 mForceNoOverlappingRendering = force;
3250 }
3251
3252 @Override
3253 public boolean hasOverlappingRendering() {
3254 return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
3255 }
3256
Selim Cinek6811d722016-01-19 17:53:12 -08003257 public void setAnimationRunning(boolean animationRunning) {
3258 if (animationRunning != mAnimationRunning) {
3259 if (animationRunning) {
3260 getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
3261 } else {
3262 getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
3263 }
3264 mAnimationRunning = animationRunning;
3265 }
3266 }
3267
Selim Cinek3776fe02016-02-04 13:32:43 -08003268 public boolean isExpanded() {
3269 return mIsExpanded;
3270 }
3271
Selim Cinek3afd00e2014-08-11 22:32:57 +02003272 /**
Christoph Studer6e3eceb2014-04-01 18:40:27 +02003273 * A listener that is notified when some child locations might have changed.
3274 */
3275 public interface OnChildLocationsChangedListener {
3276 public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout);
3277 }
Selim Cinek572bbd42014-04-25 16:43:27 +02003278
Jorim Jaggi290600a2014-05-30 17:02:20 +02003279 /**
Selim Cinek3a9c10a2014-10-28 14:21:10 +01003280 * A listener that is notified when the empty space below the notifications is clicked on
3281 */
3282 public interface OnEmptySpaceClickListener {
3283 public void onEmptySpaceClicked(float x, float y);
3284 }
3285
3286 /**
Jorim Jaggi290600a2014-05-30 17:02:20 +02003287 * A listener that gets notified when the overscroll at the top has changed.
3288 */
3289 public interface OnOverscrollTopChangedListener {
Jorim Jaggi475b21d2014-07-01 18:13:24 +02003290
3291 /**
3292 * Notifies a listener that the overscroll has changed.
3293 *
3294 * @param amount the amount of overscroll, in pixels
3295 * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
3296 * unrubberbanded motion to directly expand overscroll view (e.g expand
3297 * QS)
3298 */
3299 public void onOverscrollTopChanged(float amount, boolean isRubberbanded);
Selim Cinek1408eb52014-06-02 14:45:38 +02003300
3301 /**
3302 * Notify a listener that the scroller wants to escape from the scrolling motion and
3303 * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
3304 *
3305 * @param velocity The velocity that the Scroller had when over flinging
3306 * @param open Should the fling open or close the overscroll view.
3307 */
3308 public void flingTopOverscroll(float velocity, boolean open);
Jorim Jaggi290600a2014-05-30 17:02:20 +02003309 }
3310
Mady Mellor4b80b102016-01-22 08:03:58 -08003311 /**
3312 * A listener that is notified when the gear is shown behind a notification.
3313 */
3314 public interface GearDisplayedListener {
3315 void onGearDisplayed(ExpandableNotificationRow row);
3316 }
3317
3318 private class NotificationSwipeHelper extends SwipeHelper {
3319 private static final int MOVE_STATE_LEFT = -1;
3320 private static final int MOVE_STATE_UNDEFINED = 0;
3321 private static final int MOVE_STATE_RIGHT = 1;
3322
3323 private static final long GEAR_SHOW_DELAY = 60;
3324
3325 private ArrayList<View> mTranslatingViews = new ArrayList<>();
3326 private CheckForDrag mCheckForDrag;
3327 private Handler mHandler;
3328 private int mMoveState = MOVE_STATE_UNDEFINED;
3329
3330 public NotificationSwipeHelper(int swipeDirection, Callback callback, Context context) {
3331 super(swipeDirection, callback, context);
3332 mHandler = new Handler();
3333 }
3334
3335 @Override
3336 public void onDownUpdate(View currView) {
3337 // Set the active view
3338 mTranslatingParentView = currView;
3339
3340 // Reset check for drag gesture
3341 mCheckForDrag = null;
3342
3343 // Slide back any notifications that might be showing a gear
3344 resetExposedGearView();
3345
3346 if (currView instanceof ExpandableNotificationRow) {
3347 // Set the listener for the current row's gear
3348 mCurrIconRow = ((ExpandableNotificationRow) currView).getSettingsRow();
3349 mCurrIconRow.setGearListener(NotificationStackScrollLayout.this);
3350
3351 // And the translating children
3352 mTranslatingViews = ((ExpandableNotificationRow) currView).getContentViews();
3353 }
3354 mMoveState = MOVE_STATE_UNDEFINED;
3355 }
3356
3357 @Override
3358 public void onMoveUpdate(View view, float translation, float delta) {
3359 final int newMoveState = (delta < 0) ? MOVE_STATE_RIGHT : MOVE_STATE_LEFT;
3360 if (mMoveState != MOVE_STATE_UNDEFINED && mMoveState != newMoveState) {
3361 // Changed directions, make sure we check for drag again.
3362 mCheckForDrag = null;
3363 }
3364 mMoveState = newMoveState;
3365
3366 if (view instanceof ExpandableNotificationRow) {
3367 ((ExpandableNotificationRow) view).setTranslationForOutline(translation);
3368 if (!isPinnedHeadsUp(view)) {
3369 // Only show the gear if we're not a heads up view.
3370 checkForDrag();
3371 if (mCurrIconRow != null) {
3372 mCurrIconRow.updateSettingsIcons(translation, getSize(view));
3373 }
3374 }
3375 }
3376 }
3377
3378 @Override
3379 public void dismissChild(final View view, float velocity) {
3380 cancelCheckForDrag();
3381 super.dismissChild(view, velocity);
3382 }
3383
3384 @Override
3385 public void snapChild(final View animView, final float targetLeft, float velocity) {
3386 final float snapBackThreshold = getSpaceForGear(animView);
3387 final float translation = getTranslation(animView);
3388 final boolean fromLeft = translation > 0;
3389 final float absTrans = Math.abs(translation);
3390 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
3391
3392 boolean pastGear = (fromLeft && translation >= snapBackThreshold * 0.4f
3393 && translation <= notiThreshold) ||
3394 (!fromLeft && absTrans >= snapBackThreshold * 0.4f
3395 && absTrans <= notiThreshold);
3396
3397 if (pastGear && !isPinnedHeadsUp(animView)) {
3398 // bouncity
3399 final float target = fromLeft ? snapBackThreshold : -snapBackThreshold;
3400 mGearExposedView = mTranslatingParentView;
3401 if (mGearDisplayedListener != null
3402 && (animView instanceof ExpandableNotificationRow)) {
3403 mGearDisplayedListener.onGearDisplayed((ExpandableNotificationRow) animView);
3404 }
3405 super.snapChild(animView, target, velocity);
3406 } else {
3407 super.snapChild(animView, 0, velocity);
3408 }
3409 }
3410
3411 @Override
3412 public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
3413 if (mDismissAllInProgress) {
3414 // When dismissing all, we translate the entire view instead.
3415 super.onTranslationUpdate(animView, value, canBeDismissed);
3416 return;
3417 }
3418 if (animView instanceof ExpandableNotificationRow) {
3419 ((ExpandableNotificationRow) animView).setTranslationForOutline(value);
3420 }
3421 if (mCurrIconRow != null) {
3422 mCurrIconRow.updateSettingsIcons(value, getSize(animView));
3423 }
3424 }
3425
3426 @Override
3427 public Animator getViewTranslationAnimator(View v, float target,
3428 AnimatorUpdateListener listener) {
3429 if (mDismissAllInProgress) {
3430 // When dismissing all, we translate the entire view instead.
3431 return super.getViewTranslationAnimator(v, target, listener);
3432 }
3433 ArrayList<Animator> animators = new ArrayList<Animator>();
3434 for (int i = 0; i < mTranslatingViews.size(); i++) {
3435 ObjectAnimator anim = createTranslationAnimation(mTranslatingViews.get(i), target);
3436 animators.add(anim);
3437 if (i == 0 && listener != null) {
3438 anim.addUpdateListener(listener);
3439 }
3440 }
3441 AnimatorSet set = new AnimatorSet();
3442 set.playTogether(animators);
3443 return set;
3444 }
3445
3446 @Override
3447 public void setTranslation(View v, float translate) {
3448 if (mDismissAllInProgress) {
3449 // When dismissing all, we translate the entire view instead.
3450 super.setTranslation(v, translate);
3451 return;
3452 }
3453 // Translate the group of views
3454 for (int i = 0; i < mTranslatingViews.size(); i++) {
3455 if (mTranslatingViews.get(i) != null) {
3456 super.setTranslation(mTranslatingViews.get(i), translate);
3457 }
3458 }
3459 }
3460
3461 @Override
3462 public float getTranslation(View v) {
3463 if (mDismissAllInProgress) {
3464 // When dismissing all, we translate the entire view instead.
3465 return super.getTranslation(v);
3466 }
3467 // All of the views in the list should have same translation, just use first one.
3468 if (mTranslatingViews.size() > 0) {
3469 return super.getTranslation(mTranslatingViews.get(0));
3470 }
3471 return 0;
3472 }
3473
3474
3475 /**
3476 * Returns the horizontal space in pixels required to display the gear behind a
3477 * notification.
3478 */
3479 private float getSpaceForGear(View view) {
3480 if (view instanceof ExpandableNotificationRow) {
3481 return ((ExpandableNotificationRow) view).getSpaceForGear();
3482 }
3483 return 0;
3484 }
3485
3486 private void checkForDrag() {
3487 if (mCheckForDrag == null) {
3488 mCheckForDrag = new CheckForDrag();
3489 mHandler.postDelayed(mCheckForDrag, GEAR_SHOW_DELAY);
3490 }
3491 }
3492
3493 private void cancelCheckForDrag() {
3494 if (mCurrIconRow != null) {
3495 mCurrIconRow.cancelFadeAnimator();
3496 }
3497 mHandler.removeCallbacks(mCheckForDrag);
3498 mCheckForDrag = null;
3499 }
3500
3501 private final class CheckForDrag implements Runnable {
3502 @Override
3503 public void run() {
3504 final float translation = getTranslation(mTranslatingParentView);
3505 final float absTransX = Math.abs(translation);
3506 final float bounceBackToGearWidth = getSpaceForGear(mTranslatingParentView);
3507 final float notiThreshold = getSize(mTranslatingParentView) * 0.4f;
3508 if (mCurrIconRow != null && absTransX >= bounceBackToGearWidth * 0.4
3509 && absTransX < notiThreshold) {
3510 // Show icon
3511 mCurrIconRow.fadeInSettings(translation > 0 /* fromLeft */, translation,
3512 notiThreshold);
3513 } else {
3514 // Allow more to be posted if this wasn't a drag.
3515 mCheckForDrag = null;
3516 }
3517 }
3518 }
3519
3520 private void resetExposedGearView() {
3521 if (mGearExposedView == null || mGearExposedView == mTranslatingParentView) {
3522 // If no gear is showing or it's showing for this view we do nothing.
3523 return;
3524 }
3525
3526 final View prevGearExposedView = mGearExposedView;
3527 mGearExposedView = null;
3528
3529 AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
3530 public void onAnimationEnd(Animator animator) {
3531 if (prevGearExposedView instanceof ExpandableNotificationRow) {
3532 ((ExpandableNotificationRow) prevGearExposedView).getSettingsRow()
3533 .resetState();
3534 }
3535 }
3536 };
3537 AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
3538 @Override
3539 public void onAnimationUpdate(ValueAnimator animation) {
3540 if (prevGearExposedView instanceof ExpandableNotificationRow) {
3541 ((ExpandableNotificationRow) prevGearExposedView)
3542 .setTranslationForOutline((float) animation.getAnimatedValue());
3543 }
3544 }
3545 };
3546 Animator set = getViewTranslationAnimator(prevGearExposedView, 0, updateListener);
3547 set.addListener(listener);
3548 set.start();
3549 }
3550 }
3551
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003552 static class AnimationEvent {
Selim Cinek572bbd42014-04-25 16:43:27 +02003553
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003554 static AnimationFilter[] FILTERS = new AnimationFilter[] {
3555
3556 // ANIMATION_TYPE_ADD
3557 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003558 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003559 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003560 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003561 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003562 .animateZ()
3563 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003564
3565 // ANIMATION_TYPE_REMOVE
3566 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003567 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003568 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003569 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003570 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003571 .animateZ()
3572 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003573
3574 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3575 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003576 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003577 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003578 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003579 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003580 .animateZ()
3581 .hasDelays(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003582
3583 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3584 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003585 .animateShadowAlpha()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003586 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003587 .animateTopInset()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003588 .animateY()
3589 .animateDimmed()
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003590 .animateZ(),
3591
3592 // ANIMATION_TYPE_START_DRAG
3593 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003594 .animateShadowAlpha(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003595
3596 // ANIMATION_TYPE_SNAP_BACK
3597 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003598 .animateShadowAlpha()
Jorim Jaggidbc3dce2014-08-01 01:16:36 +02003599 .animateHeight(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003600
3601 // ANIMATION_TYPE_ACTIVATED_CHILD
3602 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003603 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003604
3605 // ANIMATION_TYPE_DIMMED
3606 new AnimationFilter()
Selim Cinek34c0a8d2014-05-12 00:01:43 +02003607 .animateY()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003608 .animateDimmed(),
3609
3610 // ANIMATION_TYPE_CHANGE_POSITION
3611 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003612 .animateAlpha() // maybe the children change positions
3613 .animateShadowAlpha()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003614 .animateHeight()
Selim Cinek708a6c12014-05-28 14:16:02 +02003615 .animateTopInset()
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003616 .animateY()
John Spurlockbf370992014-06-17 13:58:31 -04003617 .animateZ(),
3618
3619 // ANIMATION_TYPE_DARK
3620 new AnimationFilter()
Jorim Jaggi4e857f42014-11-17 19:14:04 +01003621 .animateDark()
3622 .hasDelays(),
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003623
3624 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3625 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003626 .animateShadowAlpha()
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003627 .animateHeight()
3628 .animateTopInset()
3629 .animateY()
3630 .animateDimmed()
Jorim Jaggiae441282014-08-01 02:45:18 +02003631 .animateZ()
3632 .hasDelays(),
3633
3634 // ANIMATION_TYPE_HIDE_SENSITIVE
3635 new AnimationFilter()
3636 .animateHideSensitive(),
Selim Cineka5e211b2014-08-11 17:35:48 +02003637
3638 // ANIMATION_TYPE_VIEW_RESIZE
3639 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003640 .animateShadowAlpha()
Selim Cineka5e211b2014-08-11 17:35:48 +02003641 .animateHeight()
3642 .animateTopInset()
3643 .animateY()
3644 .animateZ(),
Selim Cinekd9acca52014-09-01 22:33:25 +02003645
Selim Cinekb5605e52015-02-20 18:21:41 +01003646 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
3647 new AnimationFilter()
3648 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003649 .animateShadowAlpha()
Selim Cinekb5605e52015-02-20 18:21:41 +01003650 .animateHeight()
3651 .animateTopInset()
3652 .animateY()
3653 .animateZ(),
3654
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003655 // ANIMATION_TYPE_HEADS_UP_APPEAR
3656 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003657 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003658 .animateHeight()
3659 .animateTopInset()
3660 .animateY()
3661 .animateZ(),
3662
3663 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3664 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003665 .animateShadowAlpha()
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003666 .animateHeight()
3667 .animateTopInset()
3668 .animateY()
3669 .animateZ(),
3670
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003671 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3672 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003673 .animateShadowAlpha()
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003674 .animateHeight()
3675 .animateTopInset()
3676 .animateY()
3677 .animateZ()
3678 .hasDelays(),
3679
Selim Cineka59ecc32015-04-07 10:51:49 -07003680 // ANIMATION_TYPE_HEADS_UP_OTHER
3681 new AnimationFilter()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003682 .animateShadowAlpha()
Selim Cineka59ecc32015-04-07 10:51:49 -07003683 .animateHeight()
3684 .animateTopInset()
3685 .animateY()
3686 .animateZ(),
3687
Selim Cinekd9acca52014-09-01 22:33:25 +02003688 // ANIMATION_TYPE_EVERYTHING
3689 new AnimationFilter()
3690 .animateAlpha()
Selim Cinek277a8aa2016-01-22 12:12:37 -08003691 .animateShadowAlpha()
Selim Cinekd9acca52014-09-01 22:33:25 +02003692 .animateDark()
Selim Cinekd9acca52014-09-01 22:33:25 +02003693 .animateDimmed()
3694 .animateHideSensitive()
3695 .animateHeight()
3696 .animateTopInset()
3697 .animateY()
3698 .animateZ(),
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003699 };
3700
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003701 static int[] LENGTHS = new int[] {
3702
3703 // ANIMATION_TYPE_ADD
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003704 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003705
3706 // ANIMATION_TYPE_REMOVE
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003707 StackStateAnimator.ANIMATION_DURATION_APPEAR_DISAPPEAR,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003708
3709 // ANIMATION_TYPE_REMOVE_SWIPED_OUT
3710 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3711
3712 // ANIMATION_TYPE_TOP_PADDING_CHANGED
3713 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3714
3715 // ANIMATION_TYPE_START_DRAG
3716 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3717
3718 // ANIMATION_TYPE_SNAP_BACK
3719 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3720
3721 // ANIMATION_TYPE_ACTIVATED_CHILD
3722 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
3723
3724 // ANIMATION_TYPE_DIMMED
3725 StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED,
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003726
3727 // ANIMATION_TYPE_CHANGE_POSITION
3728 StackStateAnimator.ANIMATION_DURATION_STANDARD,
John Spurlockbf370992014-06-17 13:58:31 -04003729
3730 // ANIMATION_TYPE_DARK
3731 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003732
3733 // ANIMATION_TYPE_GO_TO_FULL_SHADE
3734 StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
Jorim Jaggiae441282014-08-01 02:45:18 +02003735
3736 // ANIMATION_TYPE_HIDE_SENSITIVE
3737 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cineka5e211b2014-08-11 17:35:48 +02003738
3739 // ANIMATION_TYPE_VIEW_RESIZE
3740 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekd9acca52014-09-01 22:33:25 +02003741
Selim Cinekb5605e52015-02-20 18:21:41 +01003742 // ANIMATION_TYPE_GROUP_EXPANSION_CHANGED
Selim Cinek99695592016-01-12 17:51:35 -08003743 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Selim Cinekb5605e52015-02-20 18:21:41 +01003744
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003745 // ANIMATION_TYPE_HEADS_UP_APPEAR
3746 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_APPEAR,
3747
3748 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR
3749 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3750
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003751 // ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
3752 StackStateAnimator.ANIMATION_DURATION_HEADS_UP_DISAPPEAR,
3753
Selim Cineka59ecc32015-04-07 10:51:49 -07003754 // ANIMATION_TYPE_HEADS_UP_OTHER
3755 StackStateAnimator.ANIMATION_DURATION_STANDARD,
3756
Selim Cinekd9acca52014-09-01 22:33:25 +02003757 // ANIMATION_TYPE_EVERYTHING
3758 StackStateAnimator.ANIMATION_DURATION_STANDARD,
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003759 };
3760
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003761 static final int ANIMATION_TYPE_ADD = 0;
3762 static final int ANIMATION_TYPE_REMOVE = 1;
3763 static final int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
3764 static final int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
3765 static final int ANIMATION_TYPE_START_DRAG = 4;
3766 static final int ANIMATION_TYPE_SNAP_BACK = 5;
3767 static final int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
3768 static final int ANIMATION_TYPE_DIMMED = 7;
3769 static final int ANIMATION_TYPE_CHANGE_POSITION = 8;
John Spurlockbf370992014-06-17 13:58:31 -04003770 static final int ANIMATION_TYPE_DARK = 9;
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003771 static final int ANIMATION_TYPE_GO_TO_FULL_SHADE = 10;
Jorim Jaggiae441282014-08-01 02:45:18 +02003772 static final int ANIMATION_TYPE_HIDE_SENSITIVE = 11;
Selim Cineka5e211b2014-08-11 17:35:48 +02003773 static final int ANIMATION_TYPE_VIEW_RESIZE = 12;
Selim Cinekb5605e52015-02-20 18:21:41 +01003774 static final int ANIMATION_TYPE_GROUP_EXPANSION_CHANGED = 13;
Selim Cinekb8f09cf2015-03-16 17:09:28 -07003775 static final int ANIMATION_TYPE_HEADS_UP_APPEAR = 14;
3776 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR = 15;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -07003777 static final int ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK = 16;
3778 static final int ANIMATION_TYPE_HEADS_UP_OTHER = 17;
3779 static final int ANIMATION_TYPE_EVERYTHING = 18;
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003780
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003781 static final int DARK_ANIMATION_ORIGIN_INDEX_ABOVE = -1;
3782 static final int DARK_ANIMATION_ORIGIN_INDEX_BELOW = -2;
3783
Selim Cinek572bbd42014-04-25 16:43:27 +02003784 final long eventStartTime;
3785 final View changingView;
3786 final int animationType;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003787 final AnimationFilter filter;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003788 final long length;
Selim Cinek8efa6dd2014-05-19 16:27:37 +02003789 View viewAfterChangingView;
Jorim Jaggi2a5e4522014-11-24 21:45:20 +01003790 int darkAnimationOriginIndex;
Selim Cineka59ecc32015-04-07 10:51:49 -07003791 boolean headsUpFromBottom;
Selim Cinek572bbd42014-04-25 16:43:27 +02003792
Jorim Jaggi0dd68812014-05-01 19:17:37 +02003793 AnimationEvent(View view, int type) {
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003794 this(view, type, LENGTHS[type]);
3795 }
3796
3797 AnimationEvent(View view, int type, long length) {
Selim Cinek572bbd42014-04-25 16:43:27 +02003798 eventStartTime = AnimationUtils.currentAnimationTimeMillis();
3799 changingView = view;
3800 animationType = type;
Jorim Jaggid552d9d2014-05-07 19:41:13 +02003801 filter = FILTERS[type];
Jorim Jaggiff9c9c42014-08-01 05:36:22 +02003802 this.length = length;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003803 }
3804
3805 /**
3806 * Combines the length of several animation events into a single value.
3807 *
3808 * @param events The events of the lengths to combine.
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003809 * @return The combined length. Depending on the event types, this might be the maximum of
3810 * all events or the length of a specific event.
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003811 */
3812 static long combineLength(ArrayList<AnimationEvent> events) {
3813 long length = 0;
3814 int size = events.size();
3815 for (int i = 0; i < size; i++) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +02003816 AnimationEvent event = events.get(i);
3817 length = Math.max(length, event.length);
3818 if (event.animationType == ANIMATION_TYPE_GO_TO_FULL_SHADE) {
3819 return event.length;
3820 }
Jorim Jaggi5aa045c2014-05-07 21:42:40 +02003821 }
3822 return length;
Selim Cinek572bbd42014-04-25 16:43:27 +02003823 }
3824 }
3825
Selim Cinek67b22602014-03-10 15:40:16 +01003826}