blob: a75d40f9201125a83482bf4fd0e99247a8bda872 [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
17package com.android.systemui.statusbar.stack;
18
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 Cineka5703182016-05-11 21:23:16 -040024import android.view.ViewGroup;
Selim Cinek572bbd42014-04-25 16:43:27 +020025import android.view.animation.Interpolator;
Selim Cinekeb973562014-05-02 17:07:49 +020026
Winsonc0d70582016-01-29 10:24:39 -080027import com.android.systemui.Interpolators;
Selim Cinekeb973562014-05-02 17:07:49 +020028import com.android.systemui.R;
Selim Cinekb5605e52015-02-20 18:21:41 +010029import com.android.systemui.statusbar.ExpandableNotificationRow;
Selim Cinek572bbd42014-04-25 16:43:27 +020030import com.android.systemui.statusbar.ExpandableView;
Selim Cinekeccb5de2016-10-28 15:04:05 -070031import com.android.systemui.statusbar.NotificationShelf;
Selim Cinek332c23f2018-03-16 17:37:50 -070032import com.android.systemui.statusbar.StatusBarIconView;
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;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +020047 public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220;
Adrian Roosaf06bf22016-07-15 12:26:49 -070048 public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
Selim Cinek332c23f2018-03-16 17:37:50 -070049 public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 550;
50 public static final int ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED
51 = (int) (ANIMATION_DURATION_HEADS_UP_APPEAR
52 * HeadsUpAppearInterpolator.getFractionUntilOvershoot());
53 public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 300;
Rohan Shah524cf7b2018-03-15 14:40:02 -070054 public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
Selim Cinek8efa6dd2014-05-19 16:27:37 +020055 public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
56 public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
Jorim Jaggi60d07c52014-07-31 15:38:21 +020057 public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
Selim Cinekb5605e52015-02-20 18:21:41 +010058 public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -070059 public static final int ANIMATION_DELAY_HEADS_UP = 120;
Selim Cinek332c23f2018-03-16 17:37:50 -070060 public static final int ANIMATION_DELAY_HEADS_UP_CLICKED= 120;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +020061
Jorim Jaggi60d07c52014-07-31 15:38:21 +020062 private final int mGoToFullShadeAppearingTranslation;
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -070063 private final int mPulsingAppearingTranslation;
Selim Cinekbbcebde2016-11-09 18:28:20 -080064 private final ExpandableViewState mTmpState = new ExpandableViewState();
Selim Cinek0cfbef42016-11-09 19:06:36 -080065 private final AnimationProperties mAnimationProperties;
Selim Cinek572bbd42014-04-25 16:43:27 +020066 public NotificationStackScrollLayout mHostLayout;
Selim Cinekeb973562014-05-02 17:07:49 +020067 private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
68 new ArrayList<>();
Selim Cinek8efa6dd2014-05-19 16:27:37 +020069 private ArrayList<View> mNewAddChildren = new ArrayList<>();
Selim Cineka59ecc32015-04-07 10:51:49 -070070 private HashSet<View> mHeadsUpAppearChildren = new HashSet<>();
71 private HashSet<View> mHeadsUpDisappearChildren = new HashSet<>();
72 private HashSet<Animator> mAnimatorSet = new HashSet<>();
Jorim Jaggi60d07c52014-07-31 15:38:21 +020073 private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<>();
Jorim Jaggid552d9d2014-05-07 19:41:13 +020074 private AnimationFilter mAnimationFilter = new AnimationFilter();
Jorim Jaggi5aa045c2014-05-07 21:42:40 +020075 private long mCurrentLength;
Jorim Jaggidbc3dce2014-08-01 01:16:36 +020076 private long mCurrentAdditionalDelay;
Selim Cinek572bbd42014-04-25 16:43:27 +020077
Jorim Jaggi60d07c52014-07-31 15:38:21 +020078 /** The current index for the last child which was not added in this event set. */
79 private int mCurrentLastNotAddedIndex;
Selim Cinek8d9ff9c2014-05-12 15:13:04 +020080 private ValueAnimator mTopOverScrollAnimator;
81 private ValueAnimator mBottomOverScrollAnimator;
Selim Cineka59ecc32015-04-07 10:51:49 -070082 private int mHeadsUpAppearHeightBottom;
83 private boolean mShadeExpanded;
Rohan Shah524cf7b2018-03-15 14:40:02 -070084 private ArrayList<ExpandableView> mTransientViewsToRemove = new ArrayList<>();
Selim Cinekeccb5de2016-10-28 15:04:05 -070085 private NotificationShelf mShelf;
Selim Cinek332c23f2018-03-16 17:37:50 -070086 private float mStatusBarIconLocation;
87 private int[] mTmpLocation = new int[2];
Selim Cinek8d9ff9c2014-05-12 15:13:04 +020088
Selim Cinek572bbd42014-04-25 16:43:27 +020089 public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
90 mHostLayout = hostLayout;
Jorim Jaggi60d07c52014-07-31 15:38:21 +020091 mGoToFullShadeAppearingTranslation =
92 hostLayout.getContext().getResources().getDimensionPixelSize(
93 R.dimen.go_to_full_shade_appearing_translation);
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -070094 mPulsingAppearingTranslation =
95 hostLayout.getContext().getResources().getDimensionPixelSize(
96 R.dimen.pulsing_notification_appear_translation);
Selim Cinek0cfbef42016-11-09 19:06:36 -080097 mAnimationProperties = new AnimationProperties() {
98 @Override
99 public AnimationFilter getAnimationFilter() {
100 return mAnimationFilter;
101 }
102
103 @Override
104 public AnimatorListenerAdapter getAnimationFinishListener() {
105 return getGlobalAnimationFinishedListener();
106 }
107
108 @Override
109 public boolean wasAdded(View view) {
110 return mNewAddChildren.contains(view);
111 }
112
113 @Override
114 public Interpolator getCustomInterpolator(View child, Property property) {
115 if (mHeadsUpAppearChildren.contains(child) && View.TRANSLATION_Y.equals(property)) {
Selim Cinek061d9072016-12-20 13:17:03 +0100116 return Interpolators.HEADS_UP_APPEAR;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800117 }
118 return null;
119 }
120 };
Selim Cinek572bbd42014-04-25 16:43:27 +0200121 }
122
123 public boolean isRunning() {
Selim Cinekeb973562014-05-02 17:07:49 +0200124 return !mAnimatorSet.isEmpty();
Selim Cinek572bbd42014-04-25 16:43:27 +0200125 }
126
127 public void startAnimationForEvents(
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200128 ArrayList<NotificationStackScrollLayout.AnimationEvent> mAnimationEvents,
Jorim Jaggidbc3dce2014-08-01 01:16:36 +0200129 StackScrollState finalState, long additionalDelay) {
Selim Cinekeb973562014-05-02 17:07:49 +0200130
131 processAnimationEvents(mAnimationEvents, finalState);
132
Selim Cinek572bbd42014-04-25 16:43:27 +0200133 int childCount = mHostLayout.getChildCount();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200134 mAnimationFilter.applyCombination(mNewEvents);
Jorim Jaggidbc3dce2014-08-01 01:16:36 +0200135 mCurrentAdditionalDelay = additionalDelay;
Jorim Jaggi5aa045c2014-05-07 21:42:40 +0200136 mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents);
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200137 mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
Selim Cinek572bbd42014-04-25 16:43:27 +0200138 for (int i = 0; i < childCount; i++) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200139 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200140
Selim Cinekbbcebde2016-11-09 18:28:20 -0800141 ExpandableViewState viewState = finalState.getViewStateForView(child);
Selim Cineka59ecc32015-04-07 10:51:49 -0700142 if (viewState == null || child.getVisibility() == View.GONE
143 || applyWithoutAnimation(child, viewState, finalState)) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200144 continue;
145 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200146
Selim Cinek0cfbef42016-11-09 19:06:36 -0800147 initAnimationProperties(finalState, child, viewState);
148 viewState.animateTo(child, mAnimationProperties);
Selim Cinek572bbd42014-04-25 16:43:27 +0200149 }
Selim Cinekeb973562014-05-02 17:07:49 +0200150 if (!isRunning()) {
151 // no child has preformed any animation, lets finish
152 onAnimationFinished();
153 }
Selim Cineka59ecc32015-04-07 10:51:49 -0700154 mHeadsUpAppearChildren.clear();
155 mHeadsUpDisappearChildren.clear();
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200156 mNewEvents.clear();
157 mNewAddChildren.clear();
Selim Cinek572bbd42014-04-25 16:43:27 +0200158 }
159
Selim Cinek0cfbef42016-11-09 19:06:36 -0800160 private void initAnimationProperties(StackScrollState finalState, ExpandableView child,
161 ExpandableViewState viewState) {
162 boolean wasAdded = mAnimationProperties.wasAdded(child);
163 mAnimationProperties.duration = mCurrentLength;
164 adaptDurationWhenGoingToFullShade(child, viewState, wasAdded);
165 mAnimationProperties.delay = 0;
166 if (wasAdded || mAnimationFilter.hasDelays
167 && (viewState.yTranslation != child.getTranslationY()
168 || viewState.zTranslation != child.getTranslationZ()
169 || viewState.alpha != child.getAlpha()
170 || viewState.height != child.getActualHeight()
171 || viewState.clipTopAmount != child.getClipTopAmount()
172 || viewState.dark != child.isDark()
173 || viewState.shadowAlpha != child.getShadowAlpha())) {
174 mAnimationProperties.delay = mCurrentAdditionalDelay
175 + calculateChildAnimationDelay(viewState, finalState);
176 }
177 }
178
179 private void adaptDurationWhenGoingToFullShade(ExpandableView child,
180 ExpandableViewState viewState, boolean wasAdded) {
181 if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
182 child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
183 float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
184 longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
185 mAnimationProperties.duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
186 (long) (100 * longerDurationFactor);
187 }
188 }
189
Selim Cineka59ecc32015-04-07 10:51:49 -0700190 /**
191 * Determines if a view should not perform an animation and applies it directly.
192 *
193 * @return true if no animation should be performed
194 */
Selim Cinekbbcebde2016-11-09 18:28:20 -0800195 private boolean applyWithoutAnimation(ExpandableView child, ExpandableViewState viewState,
Selim Cineka59ecc32015-04-07 10:51:49 -0700196 StackScrollState finalState) {
197 if (mShadeExpanded) {
198 return false;
199 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800200 if (ViewState.isAnimatingY(child)) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700201 // A Y translation animation is running
202 return false;
203 }
204 if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
205 // This is a heads up animation
206 return false;
207 }
Selim Cinek131c1e22015-05-11 19:04:49 -0700208 if (NotificationStackScrollLayout.isPinnedHeadsUp(child)) {
Selim Cinek1f3f5442015-04-10 17:54:46 -0700209 // This is another headsUp which might move. Let's animate!
210 return false;
211 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800212 viewState.applyToView(child);
Selim Cineka59ecc32015-04-07 10:51:49 -0700213 return true;
214 }
215
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200216 private int findLastNotAddedIndex(StackScrollState finalState) {
217 int childCount = mHostLayout.getChildCount();
218 for (int i = childCount - 1; i >= 0; i--) {
219 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
220
Selim Cinekbbcebde2016-11-09 18:28:20 -0800221 ExpandableViewState viewState = finalState.getViewStateForView(child);
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200222 if (viewState == null || child.getVisibility() == View.GONE) {
223 continue;
224 }
225 if (!mNewAddChildren.contains(child)) {
226 return viewState.notGoneIndex;
227 }
228 }
229 return -1;
230 }
231
Selim Cinekbbcebde2016-11-09 18:28:20 -0800232 private long calculateChildAnimationDelay(ExpandableViewState viewState,
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200233 StackScrollState finalState) {
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;
246 int changingIndex = finalState
247 .getViewStateForView(event.changingView).notGoneIndex;
248 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;
260 View viewAfterChangingView = noNextView
261 ? mHostLayout.getLastChildNotGone()
262 : 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 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200270 int nextIndex = finalState
271 .getViewStateForView(viewAfterChangingView).notGoneIndex;
272 if (ownIndex >= nextIndex) {
273 // we only have the view afterwards
274 ownIndex++;
275 }
276 int difference = Math.abs(ownIndex - nextIndex);
277 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
278 difference - 1));
279 long delay = difference * delayPerElement;
280 minDelay = Math.max(delay, minDelay);
281 break;
282 }
283 default:
284 break;
285 }
286 }
287 return minDelay;
Selim Cinek572bbd42014-04-25 16:43:27 +0200288 }
289
Selim Cinekbbcebde2016-11-09 18:28:20 -0800290 private long calculateDelayGoToFullShade(ExpandableViewState viewState) {
Selim Cinekeccb5de2016-10-28 15:04:05 -0700291 int shelfIndex = mShelf.getNotGoneIndex();
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200292 float index = viewState.notGoneIndex;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700293 long result = 0;
294 if (index > shelfIndex) {
295 float diff = index - shelfIndex;
296 diff = (float) Math.pow(diff, 0.7f);
297 result += (long) (diff * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE * 0.25);
298 index = shelfIndex;
299 }
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200300 index = (float) Math.pow(index, 0.7f);
Selim Cinekeccb5de2016-10-28 15:04:05 -0700301 result += (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
302 return result;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200303 }
304
Selim Cinek39610542014-04-30 18:57:47 +0200305 /**
Selim Cinekeb973562014-05-02 17:07:49 +0200306 * @return an adapter which ensures that onAnimationFinished is called once no animation is
307 * running anymore
308 */
309 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
310 if (!mAnimationListenerPool.empty()) {
311 return mAnimationListenerPool.pop();
312 }
313
314 // We need to create a new one, no reusable ones found
315 return new AnimatorListenerAdapter() {
316 private boolean mWasCancelled;
317
318 @Override
319 public void onAnimationEnd(Animator animation) {
320 mAnimatorSet.remove(animation);
321 if (mAnimatorSet.isEmpty() && !mWasCancelled) {
322 onAnimationFinished();
323 }
324 mAnimationListenerPool.push(this);
325 }
326
327 @Override
328 public void onAnimationCancel(Animator animation) {
329 mWasCancelled = true;
330 }
331
332 @Override
333 public void onAnimationStart(Animator animation) {
Selim Cinekeb973562014-05-02 17:07:49 +0200334 mWasCancelled = false;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800335 mAnimatorSet.add(animation);
Selim Cinekeb973562014-05-02 17:07:49 +0200336 }
337 };
Selim Cinekeb973562014-05-02 17:07:49 +0200338 }
339
Selim Cinekeb973562014-05-02 17:07:49 +0200340 private void onAnimationFinished() {
Selim Cinekeb973562014-05-02 17:07:49 +0200341 mHostLayout.onChildAnimationFinished();
Rohan Shah524cf7b2018-03-15 14:40:02 -0700342
343 for (ExpandableView transientViewsToRemove : mTransientViewsToRemove) {
344 transientViewsToRemove.getTransientContainer()
345 .removeTransientView(transientViewsToRemove);
346 }
347 mTransientViewsToRemove.clear();
Selim Cinekeb973562014-05-02 17:07:49 +0200348 }
349
350 /**
351 * Process the animationEvents for a new animation
352 *
353 * @param animationEvents the animation events for the animation to perform
Selim Cinek39610542014-04-30 18:57:47 +0200354 * @param finalState the final state to animate to
355 */
Selim Cinekeb973562014-05-02 17:07:49 +0200356 private void processAnimationEvents(
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200357 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
Selim Cinek572bbd42014-04-25 16:43:27 +0200358 StackScrollState finalState) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200359 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200360 final ExpandableView changingView = (ExpandableView) event.changingView;
361 if (event.animationType ==
362 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200363
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200364 // This item is added, initialize it's properties.
Selim Cinekbbcebde2016-11-09 18:28:20 -0800365 ExpandableViewState viewState = finalState
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200366 .getViewStateForView(changingView);
Selim Cinek69594fb2018-05-18 12:06:10 -0700367 if (viewState == null || viewState.gone) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200368 // The position for this child was never generated, let's continue.
369 continue;
Selim Cinek39610542014-04-30 18:57:47 +0200370 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800371 viewState.applyToView(changingView);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200372 mNewAddChildren.add(changingView);
373
374 } else if (event.animationType ==
375 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
Selim Cinek5b5beb012016-11-08 18:11:58 -0800376 if (changingView.getVisibility() != View.VISIBLE) {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700377 removeTransientView(changingView);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200378 continue;
379 }
380
381 // Find the amount to translate up. This is needed in order to understand the
382 // direction of the remove animation (either downwards or upwards)
Selim Cinekbbcebde2016-11-09 18:28:20 -0800383 ExpandableViewState viewState = finalState
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200384 .getViewStateForView(event.viewAfterChangingView);
385 int actualHeight = changingView.getActualHeight();
386 // upwards by default
387 float translationDirection = -1.0f;
388 if (viewState != null) {
Selim Cinekef8c2252017-02-10 14:52:18 -0800389 float ownPosition = changingView.getTranslationY();
390 if (changingView instanceof ExpandableNotificationRow
391 && event.viewAfterChangingView instanceof ExpandableNotificationRow) {
392 ExpandableNotificationRow changingRow =
393 (ExpandableNotificationRow) changingView;
394 ExpandableNotificationRow nextRow =
395 (ExpandableNotificationRow) event.viewAfterChangingView;
396 if (changingRow.isRemoved()
397 && changingRow.wasChildInGroupWhenRemoved()
398 && !nextRow.isChildInGroup()) {
399 // the next row isn't actually a child from a group! Let's
400 // compare absolute positions!
401 ownPosition = changingRow.getTranslationWhenRemoved();
402 }
403 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200404 // there was a view after this one, Approximate the distance the next child
405 // travelled
406 translationDirection = ((viewState.yTranslation
Selim Cinekef8c2252017-02-10 14:52:18 -0800407 - (ownPosition + actualHeight / 2.0f)) * 2 /
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200408 actualHeight);
409 translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
410
411 }
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200412 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
Selim Cinek332c23f2018-03-16 17:37:50 -0700413 0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
414 0, new Runnable() {
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200415 @Override
416 public void run() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700417 removeTransientView(changingView);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200418 }
Selim Cinek332c23f2018-03-16 17:37:50 -0700419 }, null);
Selim Cinekb036ca42015-02-20 15:56:28 +0100420 } else if (event.animationType ==
Selim Cinekf336f4c2014-11-12 16:58:16 +0100421 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
Selim Cinekd1395642016-04-28 12:22:42 -0700422 if (Math.abs(changingView.getTranslation()) == changingView.getWidth()
423 && changingView.getTransientContainer() != null) {
424 changingView.getTransientContainer().removeTransientView(changingView);
425 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100426 } else if (event.animationType == NotificationStackScrollLayout
427 .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
428 ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
429 row.prepareExpansionChanged(finalState);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700430 } else if (event.animationType == NotificationStackScrollLayout
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -0700431 .AnimationEvent.ANIMATION_TYPE_PULSE_APPEAR) {
432 ExpandableViewState viewState = finalState.getViewStateForView(changingView);
433 mTmpState.copyFrom(viewState);
434 mTmpState.yTranslation += mPulsingAppearingTranslation;
435 mTmpState.alpha = 0;
436 mTmpState.applyToView(changingView);
437 } else if (event.animationType == NotificationStackScrollLayout
438 .AnimationEvent.ANIMATION_TYPE_PULSE_DISAPPEAR) {
439 ExpandableViewState viewState = finalState.getViewStateForView(changingView);
440 viewState.yTranslation += mPulsingAppearingTranslation;
441 viewState.alpha = 0;
442 } else if (event.animationType == NotificationStackScrollLayout
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700443 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
444 // This item is added, initialize it's properties.
Selim Cinekbbcebde2016-11-09 18:28:20 -0800445 ExpandableViewState viewState = finalState.getViewStateForView(changingView);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700446 mTmpState.copyFrom(viewState);
Selim Cineka59ecc32015-04-07 10:51:49 -0700447 if (event.headsUpFromBottom) {
448 mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
449 } else {
Selim Cinek332c23f2018-03-16 17:37:50 -0700450 mTmpState.yTranslation = 0;
451 changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED,
452 true /* isHeadsUpAppear */);
Selim Cineka59ecc32015-04-07 10:51:49 -0700453 }
454 mHeadsUpAppearChildren.add(changingView);
Selim Cinekbbcebde2016-11-09 18:28:20 -0800455 mTmpState.applyToView(changingView);
Selim Cineka59ecc32015-04-07 10:51:49 -0700456 } else if (event.animationType == NotificationStackScrollLayout
Jorim Jaggi5eb67c22015-08-19 19:50:49 -0700457 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
458 event.animationType == NotificationStackScrollLayout
459 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700460 mHeadsUpDisappearChildren.add(changingView);
Selim Cinek332c23f2018-03-16 17:37:50 -0700461 Runnable endRunnable = null;
462 // We need some additional delay in case we were removed to make sure we're not
463 // lagging
464 int extraDelay = event.animationType == NotificationStackScrollLayout
465 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
466 ? ANIMATION_DELAY_HEADS_UP_CLICKED
467 : 0;
Selim Cinekef5127e2015-12-21 16:55:58 -0800468 if (changingView.getParent() == null) {
Selim Cinek9dd0d042018-05-14 18:12:42 -0700469 // This notification was actually removed, so we need to add it transiently
470 mHostLayout.addTransientView(changingView, 0);
471 changingView.setTransientContainer(mHostLayout);
Selim Cinekeaee9c02015-06-25 11:04:20 -0400472 mTmpState.initFrom(changingView);
Selim Cinek332c23f2018-03-16 17:37:50 -0700473 mTmpState.yTranslation = 0;
Selim Cinek8f937632015-06-05 15:22:42 +0200474 // We temporarily enable Y animations, the real filter will be combined
475 // afterwards anyway
476 mAnimationFilter.animateY = true;
Selim Cinek332c23f2018-03-16 17:37:50 -0700477 mAnimationProperties.delay = extraDelay + ANIMATION_DELAY_HEADS_UP;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800478 mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
479 mTmpState.animateTo(changingView, mAnimationProperties);
Selim Cinek9dd0d042018-05-14 18:12:42 -0700480 endRunnable = () -> removeTransientView(changingView);
Selim Cinek332c23f2018-03-16 17:37:50 -0700481 }
482 float targetLocation = 0;
483 boolean needsAnimation = true;
484 if (changingView instanceof ExpandableNotificationRow) {
485 ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
486 if (row.isDismissed()) {
487 needsAnimation = false;
488 }
489 StatusBarIconView icon = row.getEntry().icon;
490 if (icon.getParent() != null) {
491 icon.getLocationOnScreen(mTmpLocation);
492 float iconPosition = mTmpLocation[0] - icon.getTranslationX()
493 + ViewState.getFinalTranslationX(icon) + icon.getWidth() * 0.25f;
494 mHostLayout.getLocationOnScreen(mTmpLocation);
495 targetLocation = iconPosition - mTmpLocation[0];
496 }
497 }
498
499 if (needsAnimation) {
500 // We need to add the global animation listener, since once no animations are
501 // running anymore, the panel will instantly hide itself. We need to wait until
502 // the animation is fully finished for this though.
503 changingView.performRemoveAnimation(ANIMATION_DURATION_HEADS_UP_DISAPPEAR
504 + ANIMATION_DELAY_HEADS_UP, extraDelay, 0.0f,
505 true /* isHeadsUpAppear */, targetLocation, endRunnable,
506 getGlobalAnimationFinishedListener());
507 } else if (endRunnable != null) {
508 endRunnable.run();
Selim Cinek8f937632015-06-05 15:22:42 +0200509 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200510 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200511 mNewEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +0200512 }
513 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200514
Selim Cinek9dd0d042018-05-14 18:12:42 -0700515 public static void removeTransientView(ExpandableView viewToRemove) {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700516 if (viewToRemove.getTransientContainer() != null) {
517 viewToRemove.getTransientContainer().removeTransientView(viewToRemove);
518 }
519 }
520
Jorim Jaggi475b21d2014-07-01 18:13:24 +0200521 public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
522 final boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200523 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
Jorim Jaggi90129582014-06-02 14:44:49 +0200524 if (targetAmount == startOverScrollAmount) {
525 return;
526 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200527 cancelOverScrollAnimators(onTop);
528 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
529 targetAmount);
530 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
531 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
532 @Override
533 public void onAnimationUpdate(ValueAnimator animation) {
534 float currentOverScroll = (float) animation.getAnimatedValue();
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200535 mHostLayout.setOverScrollAmount(
Jorim Jaggi475b21d2014-07-01 18:13:24 +0200536 currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
537 isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200538 }
539 });
Selim Cinekc18010f2016-01-20 13:41:30 -0800540 overScrollAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200541 overScrollAnimator.addListener(new AnimatorListenerAdapter() {
542 @Override
543 public void onAnimationEnd(Animator animation) {
544 if (onTop) {
545 mTopOverScrollAnimator = null;
546 } else {
547 mBottomOverScrollAnimator = null;
548 }
549 }
550 });
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200551 overScrollAnimator.start();
552 if (onTop) {
553 mTopOverScrollAnimator = overScrollAnimator;
554 } else {
555 mBottomOverScrollAnimator = overScrollAnimator;
556 }
557 }
558
559 public void cancelOverScrollAnimators(boolean onTop) {
560 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
561 if (currentAnimator != null) {
562 currentAnimator.cancel();
563 }
564 }
Selim Cinek02af41e2014-10-14 15:46:43 +0200565
Selim Cineka59ecc32015-04-07 10:51:49 -0700566 public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
567 mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
568 }
569
570 public void setShadeExpanded(boolean shadeExpanded) {
571 mShadeExpanded = shadeExpanded;
572 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700573
574 public void setShelf(NotificationShelf shelf) {
575 mShelf = shelf;
576 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200577}