blob: b4c205ab980cda528bf9738ba6926013fcd76254 [file] [log] [blame]
Selim Cinek572bbd42014-04-25 16:43:27 +02001/*
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
Rohan Shah20790b82018-07-02 17:21:04 -070017package com.android.systemui.statusbar.notification.stack;
Selim Cinek572bbd42014-04-25 16:43:27 +020018
Selim Cinekeb973562014-05-02 17:07:49 +020019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Selim Cinek572bbd42014-04-25 16:43:27 +020021import android.animation.ValueAnimator;
Selim Cinek0cfbef42016-11-09 19:06:36 -080022import android.util.Property;
Selim Cinek572bbd42014-04-25 16:43:27 +020023import android.view.View;
Selim Cinek572bbd42014-04-25 16:43:27 +020024import android.view.animation.Interpolator;
Selim Cinekeb973562014-05-02 17:07:49 +020025
Lucas Dupin2a6bdfd2018-06-12 17:45:21 -070026import com.android.keyguard.KeyguardSliceView;
Winsonc0d70582016-01-29 10:24:39 -080027import com.android.systemui.Interpolators;
Selim Cinekeb973562014-05-02 17:07:49 +020028import com.android.systemui.R;
Selim Cinekeccb5de2016-10-28 15:04:05 -070029import com.android.systemui.statusbar.NotificationShelf;
Selim Cinek332c23f2018-03-16 17:37:50 -070030import com.android.systemui.statusbar.StatusBarIconView;
Gus Prevasab336792018-11-14 13:52:20 -050031import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
32import com.android.systemui.statusbar.notification.row.ExpandableView;
Selim Cinek572bbd42014-04-25 16:43:27 +020033
34import java.util.ArrayList;
Selim Cinekeb973562014-05-02 17:07:49 +020035import java.util.HashSet;
Selim Cinekeb973562014-05-02 17:07:49 +020036import java.util.Stack;
Selim Cinek572bbd42014-04-25 16:43:27 +020037
38/**
39 * An stack state animator which handles animations to new StackScrollStates
40 */
41public class StackStateAnimator {
42
Jorim Jaggi5aa045c2014-05-07 21:42:40 +020043 public static final int ANIMATION_DURATION_STANDARD = 360;
Lucas Dupin43d0d732017-11-16 11:23:49 -080044 public static final int ANIMATION_DURATION_WAKEUP = 500;
Jorim Jaggi60d07c52014-07-31 15:38:21 +020045 public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
Selim Cinek8efa6dd2014-05-19 16:27:37 +020046 public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
Lucas Dupinfb8bdbb2018-12-02 15:09:37 -080047 public static final int ANIMATION_DURATION_SWIPE = 260;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +020048 public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
Adrian Roosaf06bf22016-07-15 12:26:49 -070049 public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
Selim Cinek332c23f2018-03-16 17:37:50 -070050 public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 550;
51 public static final int ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED
52 = (int) (ANIMATION_DURATION_HEADS_UP_APPEAR
53 * HeadsUpAppearInterpolator.getFractionUntilOvershoot());
54 public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 300;
Lucas Dupin2a6bdfd2018-06-12 17:45:21 -070055 public static final int ANIMATION_DURATION_PULSE_APPEAR =
56 KeyguardSliceView.DEFAULT_ANIM_DURATION;
Rohan Shah524cf7b2018-03-15 14:40:02 -070057 public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
Selim Cinek8efa6dd2014-05-19 16:27:37 +020058 public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
59 public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
Jorim Jaggi60d07c52014-07-31 15:38:21 +020060 public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
Selim Cinekb5605e52015-02-20 18:21:41 +010061 public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -070062 public static final int ANIMATION_DELAY_HEADS_UP = 120;
Selim Cinek332c23f2018-03-16 17:37:50 -070063 public static final int ANIMATION_DELAY_HEADS_UP_CLICKED= 120;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +020064
Jorim Jaggi60d07c52014-07-31 15:38:21 +020065 private final int mGoToFullShadeAppearingTranslation;
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -070066 private final int mPulsingAppearingTranslation;
Selim Cinekbbcebde2016-11-09 18:28:20 -080067 private final ExpandableViewState mTmpState = new ExpandableViewState();
Selim Cinek0cfbef42016-11-09 19:06:36 -080068 private final AnimationProperties mAnimationProperties;
Selim Cinek572bbd42014-04-25 16:43:27 +020069 public NotificationStackScrollLayout mHostLayout;
Selim Cinekeb973562014-05-02 17:07:49 +020070 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
71 new ArrayList<>();
Selim Cinek8efa6dd2014-05-19 16:27:37 +020072 private ArrayList<View> mNewAddChildren = new ArrayList<>();
Selim Cineka59ecc32015-04-07 10:51:49 -070073 private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
74 private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
75 private HashSet<Animator> mAnimatorSet = new HashSet<>();
Jorim Jaggi60d07c52014-07-31 15:38:21 +020076 private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
Jorim Jaggid552d9d2014-05-07 19:41:13 +020077 private AnimationFilter mAnimationFilter = new AnimationFilter();
Jorim Jaggi5aa045c2014-05-07 21:42:40 +020078 private long mCurrentLength;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +020079 private long mCurrentAdditionalDelay;
Selim Cinek572bbd42014-04-25 16:43:27 +020080
Jorim Jaggi60d07c52014-07-31 15:38:21 +020081 /** The current index for the last child which was not added in this event set. */
82 private int mCurrentLastNotAddedIndex;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +020083 private ValueAnimator mTopOverScrollAnimator;
84 private ValueAnimator mBottomOverScrollAnimator;
Selim Cineka59ecc32015-04-07 10:51:49 -070085 private int mHeadsUpAppearHeightBottom;
86 private boolean mShadeExpanded;
Rohan Shah524cf7b2018-03-15 14:40:02 -070087 private ArrayList<ExpandableView> mTransientViewsToRemove = new ArrayList<>();
Selim Cinekeccb5de2016-10-28 15:04:05 -070088 private NotificationShelf mShelf;
Selim Cinek332c23f2018-03-16 17:37:50 -070089 private float mStatusBarIconLocation;
90 private int[] mTmpLocation = new int[2];
Selim Cinek8d9ff9c2014-05-12 15:13:04 +020091
Selim Cinek572bbd42014-04-25 16:43:27 +020092 public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
93 mHostLayout = hostLayout;
Jorim Jaggi60d07c52014-07-31 15:38:21 +020094 mGoToFullShadeAppearingTranslation =
95 hostLayout.getContext().getResources().getDimensionPixelSize(
96 R.dimen.go_to_full_shade_appearing_translation);
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -070097 mPulsingAppearingTranslation =
98 hostLayout.getContext().getResources().getDimensionPixelSize(
99 R.dimen.pulsing_notification_appear_translation);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800100 mAnimationProperties = new AnimationProperties() {
101 @Override
102 public AnimationFilter getAnimationFilter() {
103 return mAnimationFilter;
104 }
105
106 @Override
107 public AnimatorListenerAdapter getAnimationFinishListener() {
108 return getGlobalAnimationFinishedListener();
109 }
110
111 @Override
112 public boolean wasAdded(View view) {
113 return mNewAddChildren.contains(view);
114 }
115
116 @Override
117 public Interpolator getCustomInterpolator(View child, Property property) {
118 if (mHeadsUpAppearChildren.contains(child) && View.TRANSLATION_Y.equals(property)) {
Selim Cinek061d9072016-12-20 13:17:03 +0100119 return Interpolators.HEADS_UP_APPEAR;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800120 }
121 return null;
122 }
123 };
Selim Cinek572bbd42014-04-25 16:43:27 +0200124 }
125
126 public boolean isRunning() {
Selim Cinekeb973562014-05-02 17:07:49 +0200127 return !mAnimatorSet.isEmpty();
Selim Cinek572bbd42014-04-25 16:43:27 +0200128 }
129
130 public void startAnimationForEvents(
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200131 ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500132 long additionalDelay) {
Selim Cinekeb973562014-05-02 17:07:49 +0200133
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500134 processAnimationEvents(mAnimationEvents);
Selim Cinekeb973562014-05-02 17:07:49 +0200135
Selim Cinek572bbd42014-04-25 16:43:27 +0200136 int childCount = mHostLayout.getChildCount();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200137 mAnimationFilter.applyCombination(mNewEvents);
Jorim Jaggidbc3dce2014-08-01 01:16:36 +0200138 mCurrentAdditionalDelay = additionalDelay;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +0200139 mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500140 mCurrentLastNotAddedIndex = findLastNotAddedIndex();
Selim Cinek572bbd42014-04-25 16:43:27 +0200141 for (int i = 0; i < childCount; i++) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200142 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200143
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500144 ExpandableViewState viewState = child.getViewState();
Selim Cineka59ecc32015-04-07 10:51:49 -0700145 if (viewState == null || child.getVisibility() == View.GONE
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500146 || applyWithoutAnimation(child, viewState)) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200147 continue;
148 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200149
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500150 initAnimationProperties(child, viewState);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800151 viewState.animateTo(child, mAnimationProperties);
Selim Cinek572bbd42014-04-25 16:43:27 +0200152 }
Selim Cinekeb973562014-05-02 17:07:49 +0200153 if (!isRunning()) {
154 // no child has preformed any animation, lets finish
155 onAnimationFinished();
156 }
Selim Cineka59ecc32015-04-07 10:51:49 -0700157 mHeadsUpAppearChildren.clear();
158 mHeadsUpDisappearChildren.clear();
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200159 mNewEvents.clear();
160 mNewAddChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +0200161 }
162
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500163 private void initAnimationProperties(ExpandableView child,
Selim Cinek0cfbef42016-11-09 19:06:36 -0800164 ExpandableViewState viewState) {
165 boolean wasAdded = mAnimationProperties.wasAdded(child);
166 mAnimationProperties.duration = mCurrentLength;
167 adaptDurationWhenGoingToFullShade(child, viewState, wasAdded);
168 mAnimationProperties.delay = 0;
169 if (wasAdded || mAnimationFilter.hasDelays
170 && (viewState.yTranslation != child.getTranslationY()
171 || viewState.zTranslation != child.getTranslationZ()
172 || viewState.alpha != child.getAlpha()
173 || viewState.height != child.getActualHeight()
174 || viewState.clipTopAmount != child.getClipTopAmount()
Selim Cinekff2ffec2018-11-19 18:52:01 -0800175 || viewState.dark != child.isDark())) {
Selim Cinek0cfbef42016-11-09 19:06:36 -0800176 mAnimationProperties.delay = mCurrentAdditionalDelay
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500177 + calculateChildAnimationDelay(viewState);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800178 }
179 }
180
181 private void adaptDurationWhenGoingToFullShade(ExpandableView child,
182 ExpandableViewState viewState, boolean wasAdded) {
183 if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
184 child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
185 float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
186 longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
187 mAnimationProperties.duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
188 (long) (100 * longerDurationFactor);
189 }
190 }
191
Selim Cineka59ecc32015-04-07 10:51:49 -0700192 /**
193 * Determines if a view should not perform an animation and applies it directly.
194 *
195 * @return true if no animation should be performed
196 */
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500197 private boolean applyWithoutAnimation(ExpandableView child, ExpandableViewState viewState) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700198 if (mShadeExpanded) {
199 return false;
200 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800201 if (ViewState.isAnimatingY(child)) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700202 // A Y translation animation is running
203 return false;
204 }
205 if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
206 // This is a heads up animation
207 return false;
208 }
Selim Cinek131c1e22015-05-11 19:04:49 -0700209 if (NotificationStackScrollLayout.isPinnedHeadsUp(child)) {
Selim Cinek1f3f5442015-04-10 17:54:46 -0700210 // This is another headsUp which might move. Let's animate!
211 return false;
212 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800213 viewState.applyToView(child);
Selim Cineka59ecc32015-04-07 10:51:49 -0700214 return true;
215 }
216
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500217 private int findLastNotAddedIndex() {
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200218 int childCount = mHostLayout.getChildCount();
219 for (int i = childCount - 1; i >= 0; i--) {
220 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
221
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500222 ExpandableViewState viewState = child.getViewState();
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200223 if (viewState == null || child.getVisibility() == View.GONE) {
224 continue;
225 }
226 if (!mNewAddChildren.contains(child)) {
227 return viewState.notGoneIndex;
228 }
229 }
230 return -1;
231 }
232
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500233 private long calculateChildAnimationDelay(ExpandableViewState viewState) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200234 if (mAnimationFilter.hasGoToFullShadeEvent) {
235 return calculateDelayGoToFullShade(viewState);
236 }
Selim Cinek332c23f2018-03-16 17:37:50 -0700237 if (mAnimationFilter.customDelay != AnimationFilter.NO_DELAY) {
238 return mAnimationFilter.customDelay;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -0700239 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200240 long minDelay = 0;
241 for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
242 long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
243 switch (event.animationType) {
244 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
245 int ownIndex = viewState.notGoneIndex;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500246 int changingIndex =
247 ((ExpandableView) (event.mChangingView)).getViewState().notGoneIndex;
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200248 int difference = Math.abs(ownIndex - changingIndex);
249 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
250 difference - 1));
251 long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
252 minDelay = Math.max(delay, minDelay);
253 break;
254 }
255 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
256 delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
257 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
258 int ownIndex = viewState.notGoneIndex;
259 boolean noNextView = event.viewAfterChangingView == null;
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500260 ExpandableView viewAfterChangingView = noNextView
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200261 ? mHostLayout.getLastChildNotGone()
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500262 : (ExpandableView) event.viewAfterChangingView;
Selim Cinek0c87f872016-12-13 13:01:20 -0800263 if (viewAfterChangingView == null) {
264 // This can happen when the last view in the list is removed.
265 // Since the shelf is still around and the only view, the code still goes
266 // in here and tries to calculate the delay for it when case its properties
267 // have changed.
268 continue;
269 }
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500270 int nextIndex = viewAfterChangingView.getViewState().notGoneIndex;
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200271 if (ownIndex >= nextIndex) {
272 // we only have the view afterwards
273 ownIndex++;
274 }
275 int difference = Math.abs(ownIndex - nextIndex);
276 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
277 difference - 1));
278 long delay = difference * delayPerElement;
279 minDelay = Math.max(delay, minDelay);
280 break;
281 }
282 default:
283 break;
284 }
285 }
286 return minDelay;
Selim Cinek572bbd42014-04-25 16:43:27 +0200287 }
288
Selim Cinekbbcebde2016-11-09 18:28:20 -0800289 private long calculateDelayGoToFullShade(ExpandableViewState viewState) {
Selim Cinekeccb5de2016-10-28 15:04:05 -0700290 int shelfIndex = mShelf.getNotGoneIndex();
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200291 float index = viewState.notGoneIndex;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700292 long result = 0;
293 if (index > shelfIndex) {
294 float diff = index - shelfIndex;
295 diff = (float) Math.pow(diff, 0.7f);
296 result += (long) (diff * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE * 0.25);
297 index = shelfIndex;
298 }
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200299 index = (float) Math.pow(index, 0.7f);
Selim Cinekeccb5de2016-10-28 15:04:05 -0700300 result += (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
301 return result;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200302 }
303
Selim Cinek39610542014-04-30 18:57:47 +0200304 /**
Selim Cinekeb973562014-05-02 17:07:49 +0200305 * @return an adapter which ensures that onAnimationFinished is called once no animation is
306 * running anymore
307 */
308 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
309 if (!mAnimationListenerPool.empty()) {
310 return mAnimationListenerPool.pop();
311 }
312
313 // We need to create a new one, no reusable ones found
314 return new AnimatorListenerAdapter() {
315 private boolean mWasCancelled;
316
317 @Override
318 public void onAnimationEnd(Animator animation) {
319 mAnimatorSet.remove(animation);
320 if (mAnimatorSet.isEmpty() && !mWasCancelled) {
321 onAnimationFinished();
322 }
323 mAnimationListenerPool.push(this);
324 }
325
326 @Override
327 public void onAnimationCancel(Animator animation) {
328 mWasCancelled = true;
329 }
330
331 @Override
332 public void onAnimationStart(Animator animation) {
Selim Cinekeb973562014-05-02 17:07:49 +0200333 mWasCancelled = false;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800334 mAnimatorSet.add(animation);
Selim Cinekeb973562014-05-02 17:07:49 +0200335 }
336 };
Selim Cinekeb973562014-05-02 17:07:49 +0200337 }
338
Selim Cinekeb973562014-05-02 17:07:49 +0200339 private void onAnimationFinished() {
Selim Cinekeb973562014-05-02 17:07:49 +0200340 mHostLayout.onChildAnimationFinished();
Rohan Shah524cf7b2018-03-15 14:40:02 -0700341
342 for (ExpandableView transientViewsToRemove : mTransientViewsToRemove) {
343 transientViewsToRemove.getTransientContainer()
344 .removeTransientView(transientViewsToRemove);
345 }
346 mTransientViewsToRemove.clear();
Selim Cinekeb973562014-05-02 17:07:49 +0200347 }
348
349 /**
350 * Process the animationEvents for a new animation
351 *
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500352 * @param animationEvents the animation events for the animation to perform
Selim Cinek39610542014-04-30 18:57:47 +0200353 */
Selim Cinekeb973562014-05-02 17:07:49 +0200354 private void processAnimationEvents(
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500355 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200356 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500357 final ExpandableView changingView = (ExpandableView) event.mChangingView;
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200358 if (event.animationType ==
359 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200360
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200361 // This item is added, initialize it's properties.
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500362 ExpandableViewState viewState = changingView.getViewState();
Selim Cinek69594fb2018-05-18 12:06:10 -0700363 if (viewState == null || viewState.gone) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200364 // The position for this child was never generated, let's continue.
365 continue;
Selim Cinek39610542014-04-30 18:57:47 +0200366 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800367 viewState.applyToView(changingView);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200368 mNewAddChildren.add(changingView);
369
370 } else if (event.animationType ==
371 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
Selim Cinek5b5beb012016-11-08 18:11:58 -0800372 if (changingView.getVisibility() != View.VISIBLE) {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700373 removeTransientView(changingView);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200374 continue;
375 }
376
377 // Find the amount to translate up. This is needed in order to understand the
378 // direction of the remove animation (either downwards or upwards)
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200379 // upwards by default
380 float translationDirection = -1.0f;
Selim Cinekb1a81302018-11-30 14:41:32 -0800381 if (event.viewAfterChangingView != null) {
Selim Cinekef8c2252017-02-10 14:52:18 -0800382 float ownPosition = changingView.getTranslationY();
383 if (changingView instanceof ExpandableNotificationRow
384 && event.viewAfterChangingView instanceof ExpandableNotificationRow) {
385 ExpandableNotificationRow changingRow =
386 (ExpandableNotificationRow) changingView;
387 ExpandableNotificationRow nextRow =
388 (ExpandableNotificationRow) event.viewAfterChangingView;
389 if (changingRow.isRemoved()
390 && changingRow.wasChildInGroupWhenRemoved()
391 && !nextRow.isChildInGroup()) {
392 // the next row isn't actually a child from a group! Let's
393 // compare absolute positions!
394 ownPosition = changingRow.getTranslationWhenRemoved();
395 }
396 }
Selim Cinekb1a81302018-11-30 14:41:32 -0800397 int actualHeight = changingView.getActualHeight();
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200398 // there was a view after this one, Approximate the distance the next child
399 // travelled
Selim Cinekb1a81302018-11-30 14:41:32 -0800400 ExpandableViewState viewState =
401 ((ExpandableView) event.viewAfterChangingView).getViewState();
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200402 translationDirection = ((viewState.yTranslation
Selim Cinekef8c2252017-02-10 14:52:18 -0800403 - (ownPosition + actualHeight / 2.0f)) * 2 /
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200404 actualHeight);
405 translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
406
407 }
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200408 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
Gus Prevas211181532018-12-13 14:49:33 -0500409 0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
410 0, () -> removeTransientView(changingView), null);
Selim Cinekb036ca42015-02-20 15:56:28 +0100411 } else if (event.animationType ==
Selim Cinekf336f4c2014-11-12 16:58:16 +0100412 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
Selim Cinekd1395642016-04-28 12:22:42 -0700413 if (Math.abs(changingView.getTranslation()) == changingView.getWidth()
414 && changingView.getTransientContainer() != null) {
415 changingView.getTransientContainer().removeTransientView(changingView);
416 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100417 } else if (event.animationType == NotificationStackScrollLayout
418 .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500419 ExpandableNotificationRow row = (ExpandableNotificationRow) event.mChangingView;
420 row.prepareExpansionChanged();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700421 } else if (event.animationType == NotificationStackScrollLayout
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -0700422 .AnimationEvent.ANIMATION_TYPE_PULSE_APPEAR) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500423 ExpandableViewState viewState = changingView.getViewState();
Lucas Dupin2a6bdfd2018-06-12 17:45:21 -0700424 if (viewState != null) {
425 mTmpState.copyFrom(viewState);
426 mTmpState.yTranslation += mPulsingAppearingTranslation;
427 mTmpState.alpha = 0;
428 mTmpState.applyToView(changingView);
Lucas Dupin00be88f2019-01-03 17:50:52 -0800429
430 mTmpState.copyFrom(mShelf.getViewState());
431 mTmpState.applyToView(mShelf);
Lucas Dupin2a6bdfd2018-06-12 17:45:21 -0700432 }
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -0700433 } else if (event.animationType == NotificationStackScrollLayout
434 .AnimationEvent.ANIMATION_TYPE_PULSE_DISAPPEAR) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500435 ExpandableViewState viewState = changingView.getViewState();
Lucas Dupin2a6bdfd2018-06-12 17:45:21 -0700436 if (viewState != null) {
437 viewState.alpha = 0;
438 // We want to animate the alpha away before the view starts translating,
439 // otherwise everything will overlap and look xtra ugly.
440 float originalYTranslation = viewState.yTranslation;
441 viewState.yTranslation = changingView.getTranslationY();
442 mAnimationFilter.animateAlpha = true;
443 mAnimationProperties.duration = ANIMATION_DURATION_PULSE_APPEAR / 2;
444 viewState.animateTo(changingView, mAnimationProperties);
445 viewState.yTranslation = originalYTranslation;
446 }
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -0700447 } else if (event.animationType == NotificationStackScrollLayout
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700448 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
449 // This item is added, initialize it's properties.
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500450 ExpandableViewState viewState = changingView.getViewState();
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700451 mTmpState.copyFrom(viewState);
Selim Cineka59ecc32015-04-07 10:51:49 -0700452 if (event.headsUpFromBottom) {
453 mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
454 } else {
Selim Cinek332c23f2018-03-16 17:37:50 -0700455 mTmpState.yTranslation = 0;
456 changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED,
457 true /* isHeadsUpAppear */);
Selim Cineka59ecc32015-04-07 10:51:49 -0700458 }
459 mHeadsUpAppearChildren.add(changingView);
Selim Cinekbbcebde2016-11-09 18:28:20 -0800460 mTmpState.applyToView(changingView);
Selim Cineka59ecc32015-04-07 10:51:49 -0700461 } else if (event.animationType == NotificationStackScrollLayout
Jorim Jaggi5eb67c22015-08-19 19:50:49 -0700462 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
463 event.animationType == NotificationStackScrollLayout
464 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700465 mHeadsUpDisappearChildren.add(changingView);
Selim Cinek332c23f2018-03-16 17:37:50 -0700466 Runnable endRunnable = null;
467 // We need some additional delay in case we were removed to make sure we're not
468 // lagging
469 int extraDelay = event.animationType == NotificationStackScrollLayout
470 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
471 ? ANIMATION_DELAY_HEADS_UP_CLICKED
472 : 0;
Selim Cinekef5127e2015-12-21 16:55:58 -0800473 if (changingView.getParent() == null) {
Selim Cinek9dd0d042018-05-14 18:12:42 -0700474 // This notification was actually removed, so we need to add it transiently
475 mHostLayout.addTransientView(changingView, 0);
476 changingView.setTransientContainer(mHostLayout);
Selim Cinekeaee9c02015-06-25 11:04:20 -0400477 mTmpState.initFrom(changingView);
Selim Cinek332c23f2018-03-16 17:37:50 -0700478 mTmpState.yTranslation = 0;
Selim Cinek8f937632015-06-05 15:22:42 +0200479 // We temporarily enable Y animations, the real filter will be combined
480 // afterwards anyway
481 mAnimationFilter.animateY = true;
Selim Cinek332c23f2018-03-16 17:37:50 -0700482 mAnimationProperties.delay = extraDelay + ANIMATION_DELAY_HEADS_UP;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800483 mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
484 mTmpState.animateTo(changingView, mAnimationProperties);
Selim Cinek9dd0d042018-05-14 18:12:42 -0700485 endRunnable = () -> removeTransientView(changingView);
Selim Cinek332c23f2018-03-16 17:37:50 -0700486 }
487 float targetLocation = 0;
488 boolean needsAnimation = true;
489 if (changingView instanceof ExpandableNotificationRow) {
490 ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
491 if (row.isDismissed()) {
492 needsAnimation = false;
493 }
494 StatusBarIconView icon = row.getEntry().icon;
495 if (icon.getParent() != null) {
496 icon.getLocationOnScreen(mTmpLocation);
497 float iconPosition = mTmpLocation[0] - icon.getTranslationX()
498 + ViewState.getFinalTranslationX(icon) + icon.getWidth() * 0.25f;
499 mHostLayout.getLocationOnScreen(mTmpLocation);
500 targetLocation = iconPosition - mTmpLocation[0];
501 }
502 }
503
504 if (needsAnimation) {
505 // We need to add the global animation listener, since once no animations are
506 // running anymore, the panel will instantly hide itself. We need to wait until
507 // the animation is fully finished for this though.
Gus Prevas211181532018-12-13 14:49:33 -0500508 long removeAnimationDelay = changingView.performRemoveAnimation(
509 ANIMATION_DURATION_HEADS_UP_DISAPPEAR + ANIMATION_DELAY_HEADS_UP,
510 extraDelay, 0.0f, true /* isHeadsUpAppear */, targetLocation,
511 endRunnable, getGlobalAnimationFinishedListener());
512 mAnimationProperties.delay += removeAnimationDelay;
Selim Cinek332c23f2018-03-16 17:37:50 -0700513 } else if (endRunnable != null) {
514 endRunnable.run();
Selim Cinek8f937632015-06-05 15:22:42 +0200515 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200516 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200517 mNewEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +0200518 }
519 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200520
Selim Cinek9dd0d042018-05-14 18:12:42 -0700521 public static void removeTransientView(ExpandableView viewToRemove) {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700522 if (viewToRemove.getTransientContainer() != null) {
523 viewToRemove.getTransientContainer().removeTransientView(viewToRemove);
524 }
525 }
526
Jorim Jaggi475b21d2014-07-01 18:13:24 +0200527 public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
528 final boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200529 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
Jorim Jaggi90129582014-06-02 14:44:49 +0200530 if (targetAmount == startOverScrollAmount) {
531 return;
532 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200533 cancelOverScrollAnimators(onTop);
534 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
535 targetAmount);
536 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
537 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
538 @Override
539 public void onAnimationUpdate(ValueAnimator animation) {
540 float currentOverScroll = (float) animation.getAnimatedValue();
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200541 mHostLayout.setOverScrollAmount(
Jorim Jaggi475b21d2014-07-01 18:13:24 +0200542 currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
543 isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200544 }
545 });
Selim Cinekc18010f2016-01-20 13:41:30 -0800546 overScrollAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200547 overScrollAnimator.addListener(new AnimatorListenerAdapter() {
548 @Override
549 public void onAnimationEnd(Animator animation) {
550 if (onTop) {
551 mTopOverScrollAnimator = null;
552 } else {
553 mBottomOverScrollAnimator = null;
554 }
555 }
556 });
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200557 overScrollAnimator.start();
558 if (onTop) {
559 mTopOverScrollAnimator = overScrollAnimator;
560 } else {
561 mBottomOverScrollAnimator = overScrollAnimator;
562 }
563 }
564
565 public void cancelOverScrollAnimators(boolean onTop) {
566 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
567 if (currentAnimator != null) {
568 currentAnimator.cancel();
569 }
570 }
Selim Cinek02af41e2014-10-14 15:46:43 +0200571
Selim Cineka59ecc32015-04-07 10:51:49 -0700572 public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
573 mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
574 }
575
576 public void setShadeExpanded(boolean shadeExpanded) {
577 mShadeExpanded = shadeExpanded;
578 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700579
580 public void setShelf(NotificationShelf shelf) {
581 mShelf = shelf;
582 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200583}