blob: b83a09dd02ade3fceab156f7681675e2306554e4 [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
Lucas Dupin2a6bdfd2018-06-12 17:45:21 -070027import com.android.keyguard.KeyguardSliceView;
Winsonc0d70582016-01-29 10:24:39 -080028import com.android.systemui.Interpolators;
Selim Cinekeb973562014-05-02 17:07:49 +020029import com.android.systemui.R;
Selim Cinekb5605e52015-02-20 18:21:41 +010030import com.android.systemui.statusbar.ExpandableNotificationRow;
Selim Cinek572bbd42014-04-25 16:43:27 +020031import com.android.systemui.statusbar.ExpandableView;
Selim Cinekeccb5de2016-10-28 15:04:05 -070032import com.android.systemui.statusbar.NotificationShelf;
Selim Cinek332c23f2018-03-16 17:37:50 -070033import com.android.systemui.statusbar.StatusBarIconView;
Selim Cinek572bbd42014-04-25 16:43:27 +020034
35import java.util.ArrayList;
Selim Cinekeb973562014-05-02 17:07:49 +020036import java.util.HashSet;
Selim Cinekeb973562014-05-02 17:07:49 +020037import java.util.Stack;
Selim Cinek572bbd42014-04-25 16:43:27 +020038
39/**
40 * An stack state animator which handles animations to new StackScrollStates
41 */
42public class StackStateAnimator {
43
Jorim Jaggi5aa045c2014-05-07 21:42:40 +020044 public static final int ANIMATION_DURATION_STANDARD = 360;
Lucas Dupin43d0d732017-11-16 11:23:49 -080045 public static final int ANIMATION_DURATION_WAKEUP = 500;
Jorim Jaggi60d07c52014-07-31 15:38:21 +020046 public static final int ANIMATION_DURATION_GO_TO_FULL_SHADE = 448;
Selim Cinek8efa6dd2014-05-19 16:27:37 +020047 public static final int ANIMATION_DURATION_APPEAR_DISAPPEAR = 464;
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,
Jorim Jaggidbc3dce2014-08-01 01:16:36 +0200132 StackScrollState finalState, long additionalDelay) {
Selim Cinekeb973562014-05-02 17:07:49 +0200133
134 processAnimationEvents(mAnimationEvents, finalState);
135
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);
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200140 mCurrentLastNotAddedIndex = findLastNotAddedIndex(finalState);
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
Selim Cinekbbcebde2016-11-09 18:28:20 -0800144 ExpandableViewState viewState = finalState.getViewStateForView(child);
Selim Cineka59ecc32015-04-07 10:51:49 -0700145 if (viewState == null || child.getVisibility() == View.GONE
146 || applyWithoutAnimation(child, viewState, finalState)) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200147 continue;
148 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200149
Selim Cinek0cfbef42016-11-09 19:06:36 -0800150 initAnimationProperties(finalState, child, viewState);
151 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
Selim Cinek0cfbef42016-11-09 19:06:36 -0800163 private void initAnimationProperties(StackScrollState finalState, ExpandableView child,
164 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()
175 || viewState.dark != child.isDark()
176 || viewState.shadowAlpha != child.getShadowAlpha())) {
177 mAnimationProperties.delay = mCurrentAdditionalDelay
178 + calculateChildAnimationDelay(viewState, finalState);
179 }
180 }
181
182 private void adaptDurationWhenGoingToFullShade(ExpandableView child,
183 ExpandableViewState viewState, boolean wasAdded) {
184 if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
185 child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
186 float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
187 longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
188 mAnimationProperties.duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
189 (long) (100 * longerDurationFactor);
190 }
191 }
192
Selim Cineka59ecc32015-04-07 10:51:49 -0700193 /**
194 * Determines if a view should not perform an animation and applies it directly.
195 *
196 * @return true if no animation should be performed
197 */
Selim Cinekbbcebde2016-11-09 18:28:20 -0800198 private boolean applyWithoutAnimation(ExpandableView child, ExpandableViewState viewState,
Selim Cineka59ecc32015-04-07 10:51:49 -0700199 StackScrollState finalState) {
200 if (mShadeExpanded) {
201 return false;
202 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800203 if (ViewState.isAnimatingY(child)) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700204 // A Y translation animation is running
205 return false;
206 }
207 if (mHeadsUpDisappearChildren.contains(child) || mHeadsUpAppearChildren.contains(child)) {
208 // This is a heads up animation
209 return false;
210 }
Selim Cinek131c1e22015-05-11 19:04:49 -0700211 if (NotificationStackScrollLayout.isPinnedHeadsUp(child)) {
Selim Cinek1f3f5442015-04-10 17:54:46 -0700212 // This is another headsUp which might move. Let's animate!
213 return false;
214 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800215 viewState.applyToView(child);
Selim Cineka59ecc32015-04-07 10:51:49 -0700216 return true;
217 }
218
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200219 private int findLastNotAddedIndex(StackScrollState finalState) {
220 int childCount = mHostLayout.getChildCount();
221 for (int i = childCount - 1; i >= 0; i--) {
222 final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
223
Selim Cinekbbcebde2016-11-09 18:28:20 -0800224 ExpandableViewState viewState = finalState.getViewStateForView(child);
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200225 if (viewState == null || child.getVisibility() == View.GONE) {
226 continue;
227 }
228 if (!mNewAddChildren.contains(child)) {
229 return viewState.notGoneIndex;
230 }
231 }
232 return -1;
233 }
234
Selim Cinekbbcebde2016-11-09 18:28:20 -0800235 private long calculateChildAnimationDelay(ExpandableViewState viewState,
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200236 StackScrollState finalState) {
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200237 if (mAnimationFilter.hasGoToFullShadeEvent) {
238 return calculateDelayGoToFullShade(viewState);
239 }
Selim Cinek332c23f2018-03-16 17:37:50 -0700240 if (mAnimationFilter.customDelay != AnimationFilter.NO_DELAY) {
241 return mAnimationFilter.customDelay;
Jorim Jaggi5eb67c22015-08-19 19:50:49 -0700242 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200243 long minDelay = 0;
244 for (NotificationStackScrollLayout.AnimationEvent event : mNewEvents) {
245 long delayPerElement = ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING;
246 switch (event.animationType) {
247 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD: {
248 int ownIndex = viewState.notGoneIndex;
249 int changingIndex = finalState
250 .getViewStateForView(event.changingView).notGoneIndex;
251 int difference = Math.abs(ownIndex - changingIndex);
252 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
253 difference - 1));
254 long delay = (DELAY_EFFECT_MAX_INDEX_DIFFERENCE - difference) * delayPerElement;
255 minDelay = Math.max(delay, minDelay);
256 break;
257 }
258 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT:
259 delayPerElement = ANIMATION_DELAY_PER_ELEMENT_MANUAL;
260 case NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE: {
261 int ownIndex = viewState.notGoneIndex;
262 boolean noNextView = event.viewAfterChangingView == null;
263 View viewAfterChangingView = noNextView
264 ? mHostLayout.getLastChildNotGone()
265 : event.viewAfterChangingView;
Selim Cinek0c87f872016-12-13 13:01:20 -0800266 if (viewAfterChangingView == null) {
267 // This can happen when the last view in the list is removed.
268 // Since the shelf is still around and the only view, the code still goes
269 // in here and tries to calculate the delay for it when case its properties
270 // have changed.
271 continue;
272 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200273 int nextIndex = finalState
274 .getViewStateForView(viewAfterChangingView).notGoneIndex;
275 if (ownIndex >= nextIndex) {
276 // we only have the view afterwards
277 ownIndex++;
278 }
279 int difference = Math.abs(ownIndex - nextIndex);
280 difference = Math.max(0, Math.min(DELAY_EFFECT_MAX_INDEX_DIFFERENCE,
281 difference - 1));
282 long delay = difference * delayPerElement;
283 minDelay = Math.max(delay, minDelay);
284 break;
285 }
286 default:
287 break;
288 }
289 }
290 return minDelay;
Selim Cinek572bbd42014-04-25 16:43:27 +0200291 }
292
Selim Cinekbbcebde2016-11-09 18:28:20 -0800293 private long calculateDelayGoToFullShade(ExpandableViewState viewState) {
Selim Cinekeccb5de2016-10-28 15:04:05 -0700294 int shelfIndex = mShelf.getNotGoneIndex();
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200295 float index = viewState.notGoneIndex;
Selim Cinekeccb5de2016-10-28 15:04:05 -0700296 long result = 0;
297 if (index > shelfIndex) {
298 float diff = index - shelfIndex;
299 diff = (float) Math.pow(diff, 0.7f);
300 result += (long) (diff * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE * 0.25);
301 index = shelfIndex;
302 }
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200303 index = (float) Math.pow(index, 0.7f);
Selim Cinekeccb5de2016-10-28 15:04:05 -0700304 result += (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
305 return result;
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200306 }
307
Selim Cinek39610542014-04-30 18:57:47 +0200308 /**
Selim Cinekeb973562014-05-02 17:07:49 +0200309 * @return an adapter which ensures that onAnimationFinished is called once no animation is
310 * running anymore
311 */
312 private AnimatorListenerAdapter getGlobalAnimationFinishedListener() {
313 if (!mAnimationListenerPool.empty()) {
314 return mAnimationListenerPool.pop();
315 }
316
317 // We need to create a new one, no reusable ones found
318 return new AnimatorListenerAdapter() {
319 private boolean mWasCancelled;
320
321 @Override
322 public void onAnimationEnd(Animator animation) {
323 mAnimatorSet.remove(animation);
324 if (mAnimatorSet.isEmpty() && !mWasCancelled) {
325 onAnimationFinished();
326 }
327 mAnimationListenerPool.push(this);
328 }
329
330 @Override
331 public void onAnimationCancel(Animator animation) {
332 mWasCancelled = true;
333 }
334
335 @Override
336 public void onAnimationStart(Animator animation) {
Selim Cinekeb973562014-05-02 17:07:49 +0200337 mWasCancelled = false;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800338 mAnimatorSet.add(animation);
Selim Cinekeb973562014-05-02 17:07:49 +0200339 }
340 };
Selim Cinekeb973562014-05-02 17:07:49 +0200341 }
342
Selim Cinekeb973562014-05-02 17:07:49 +0200343 private void onAnimationFinished() {
Selim Cinekeb973562014-05-02 17:07:49 +0200344 mHostLayout.onChildAnimationFinished();
Rohan Shah524cf7b2018-03-15 14:40:02 -0700345
346 for (ExpandableView transientViewsToRemove : mTransientViewsToRemove) {
347 transientViewsToRemove.getTransientContainer()
348 .removeTransientView(transientViewsToRemove);
349 }
350 mTransientViewsToRemove.clear();
Selim Cinekeb973562014-05-02 17:07:49 +0200351 }
352
353 /**
354 * Process the animationEvents for a new animation
355 *
356 * @param animationEvents the animation events for the animation to perform
Selim Cinek39610542014-04-30 18:57:47 +0200357 * @param finalState the final state to animate to
358 */
Selim Cinekeb973562014-05-02 17:07:49 +0200359 private void processAnimationEvents(
Jorim Jaggi0dd68812014-05-01 19:17:37 +0200360 ArrayList<NotificationStackScrollLayout.AnimationEvent> animationEvents,
Selim Cinek572bbd42014-04-25 16:43:27 +0200361 StackScrollState finalState) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200362 for (NotificationStackScrollLayout.AnimationEvent event : animationEvents) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200363 final ExpandableView changingView = (ExpandableView) event.changingView;
364 if (event.animationType ==
365 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
Selim Cinek572bbd42014-04-25 16:43:27 +0200366
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200367 // This item is added, initialize it's properties.
Selim Cinekbbcebde2016-11-09 18:28:20 -0800368 ExpandableViewState viewState = finalState
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200369 .getViewStateForView(changingView);
Selim Cinek69594fb2018-05-18 12:06:10 -0700370 if (viewState == null || viewState.gone) {
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200371 // The position for this child was never generated, let's continue.
372 continue;
Selim Cinek39610542014-04-30 18:57:47 +0200373 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800374 viewState.applyToView(changingView);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200375 mNewAddChildren.add(changingView);
376
377 } else if (event.animationType ==
378 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
Selim Cinek5b5beb012016-11-08 18:11:58 -0800379 if (changingView.getVisibility() != View.VISIBLE) {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700380 removeTransientView(changingView);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200381 continue;
382 }
383
384 // Find the amount to translate up. This is needed in order to understand the
385 // direction of the remove animation (either downwards or upwards)
Selim Cinekbbcebde2016-11-09 18:28:20 -0800386 ExpandableViewState viewState = finalState
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200387 .getViewStateForView(event.viewAfterChangingView);
388 int actualHeight = changingView.getActualHeight();
389 // upwards by default
390 float translationDirection = -1.0f;
391 if (viewState != null) {
Selim Cinekef8c2252017-02-10 14:52:18 -0800392 float ownPosition = changingView.getTranslationY();
393 if (changingView instanceof ExpandableNotificationRow
394 && event.viewAfterChangingView instanceof ExpandableNotificationRow) {
395 ExpandableNotificationRow changingRow =
396 (ExpandableNotificationRow) changingView;
397 ExpandableNotificationRow nextRow =
398 (ExpandableNotificationRow) event.viewAfterChangingView;
399 if (changingRow.isRemoved()
400 && changingRow.wasChildInGroupWhenRemoved()
401 && !nextRow.isChildInGroup()) {
402 // the next row isn't actually a child from a group! Let's
403 // compare absolute positions!
404 ownPosition = changingRow.getTranslationWhenRemoved();
405 }
406 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200407 // there was a view after this one, Approximate the distance the next child
408 // travelled
409 translationDirection = ((viewState.yTranslation
Selim Cinekef8c2252017-02-10 14:52:18 -0800410 - (ownPosition + actualHeight / 2.0f)) * 2 /
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200411 actualHeight);
412 translationDirection = Math.max(Math.min(translationDirection, 1.0f),-1.0f);
413
414 }
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200415 changingView.performRemoveAnimation(ANIMATION_DURATION_APPEAR_DISAPPEAR,
Selim Cinek332c23f2018-03-16 17:37:50 -0700416 0 /* delay */, translationDirection, false /* isHeadsUpAppear */,
417 0, new Runnable() {
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200418 @Override
419 public void run() {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700420 removeTransientView(changingView);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200421 }
Selim Cinek332c23f2018-03-16 17:37:50 -0700422 }, null);
Selim Cinekb036ca42015-02-20 15:56:28 +0100423 } else if (event.animationType ==
Selim Cinekf336f4c2014-11-12 16:58:16 +0100424 NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT) {
Selim Cinekd1395642016-04-28 12:22:42 -0700425 if (Math.abs(changingView.getTranslation()) == changingView.getWidth()
426 && changingView.getTransientContainer() != null) {
427 changingView.getTransientContainer().removeTransientView(changingView);
428 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100429 } else if (event.animationType == NotificationStackScrollLayout
430 .AnimationEvent.ANIMATION_TYPE_GROUP_EXPANSION_CHANGED) {
431 ExpandableNotificationRow row = (ExpandableNotificationRow) event.changingView;
432 row.prepareExpansionChanged(finalState);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700433 } else if (event.animationType == NotificationStackScrollLayout
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -0700434 .AnimationEvent.ANIMATION_TYPE_PULSE_APPEAR) {
435 ExpandableViewState viewState = finalState.getViewStateForView(changingView);
Lucas Dupin2a6bdfd2018-06-12 17:45:21 -0700436 if (viewState != null) {
437 mTmpState.copyFrom(viewState);
438 mTmpState.yTranslation += mPulsingAppearingTranslation;
439 mTmpState.alpha = 0;
440 mTmpState.applyToView(changingView);
441 }
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -0700442 } else if (event.animationType == NotificationStackScrollLayout
443 .AnimationEvent.ANIMATION_TYPE_PULSE_DISAPPEAR) {
444 ExpandableViewState viewState = finalState.getViewStateForView(changingView);
Lucas Dupin2a6bdfd2018-06-12 17:45:21 -0700445 if (viewState != null) {
446 viewState.alpha = 0;
447 // We want to animate the alpha away before the view starts translating,
448 // otherwise everything will overlap and look xtra ugly.
449 float originalYTranslation = viewState.yTranslation;
450 viewState.yTranslation = changingView.getTranslationY();
451 mAnimationFilter.animateAlpha = true;
452 mAnimationProperties.duration = ANIMATION_DURATION_PULSE_APPEAR / 2;
453 viewState.animateTo(changingView, mAnimationProperties);
454 viewState.yTranslation = originalYTranslation;
455 }
Lucas Dupin3d7ccaf2018-04-02 21:19:23 -0700456 } else if (event.animationType == NotificationStackScrollLayout
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700457 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
458 // This item is added, initialize it's properties.
Selim Cinekbbcebde2016-11-09 18:28:20 -0800459 ExpandableViewState viewState = finalState.getViewStateForView(changingView);
Selim Cinekb8f09cf2015-03-16 17:09:28 -0700460 mTmpState.copyFrom(viewState);
Selim Cineka59ecc32015-04-07 10:51:49 -0700461 if (event.headsUpFromBottom) {
462 mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
463 } else {
Selim Cinek332c23f2018-03-16 17:37:50 -0700464 mTmpState.yTranslation = 0;
465 changingView.performAddAnimation(0, ANIMATION_DURATION_HEADS_UP_APPEAR_CLOSED,
466 true /* isHeadsUpAppear */);
Selim Cineka59ecc32015-04-07 10:51:49 -0700467 }
468 mHeadsUpAppearChildren.add(changingView);
Selim Cinekbbcebde2016-11-09 18:28:20 -0800469 mTmpState.applyToView(changingView);
Selim Cineka59ecc32015-04-07 10:51:49 -0700470 } else if (event.animationType == NotificationStackScrollLayout
Jorim Jaggi5eb67c22015-08-19 19:50:49 -0700471 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
472 event.animationType == NotificationStackScrollLayout
473 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK) {
Selim Cineka59ecc32015-04-07 10:51:49 -0700474 mHeadsUpDisappearChildren.add(changingView);
Selim Cinek332c23f2018-03-16 17:37:50 -0700475 Runnable endRunnable = null;
476 // We need some additional delay in case we were removed to make sure we're not
477 // lagging
478 int extraDelay = event.animationType == NotificationStackScrollLayout
479 .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
480 ? ANIMATION_DELAY_HEADS_UP_CLICKED
481 : 0;
Selim Cinekef5127e2015-12-21 16:55:58 -0800482 if (changingView.getParent() == null) {
Selim Cinek9dd0d042018-05-14 18:12:42 -0700483 // This notification was actually removed, so we need to add it transiently
484 mHostLayout.addTransientView(changingView, 0);
485 changingView.setTransientContainer(mHostLayout);
Selim Cinekeaee9c02015-06-25 11:04:20 -0400486 mTmpState.initFrom(changingView);
Selim Cinek332c23f2018-03-16 17:37:50 -0700487 mTmpState.yTranslation = 0;
Selim Cinek8f937632015-06-05 15:22:42 +0200488 // We temporarily enable Y animations, the real filter will be combined
489 // afterwards anyway
490 mAnimationFilter.animateY = true;
Selim Cinek332c23f2018-03-16 17:37:50 -0700491 mAnimationProperties.delay = extraDelay + ANIMATION_DELAY_HEADS_UP;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800492 mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
493 mTmpState.animateTo(changingView, mAnimationProperties);
Selim Cinek9dd0d042018-05-14 18:12:42 -0700494 endRunnable = () -> removeTransientView(changingView);
Selim Cinek332c23f2018-03-16 17:37:50 -0700495 }
496 float targetLocation = 0;
497 boolean needsAnimation = true;
498 if (changingView instanceof ExpandableNotificationRow) {
499 ExpandableNotificationRow row = (ExpandableNotificationRow) changingView;
500 if (row.isDismissed()) {
501 needsAnimation = false;
502 }
503 StatusBarIconView icon = row.getEntry().icon;
504 if (icon.getParent() != null) {
505 icon.getLocationOnScreen(mTmpLocation);
506 float iconPosition = mTmpLocation[0] - icon.getTranslationX()
507 + ViewState.getFinalTranslationX(icon) + icon.getWidth() * 0.25f;
508 mHostLayout.getLocationOnScreen(mTmpLocation);
509 targetLocation = iconPosition - mTmpLocation[0];
510 }
511 }
512
513 if (needsAnimation) {
514 // We need to add the global animation listener, since once no animations are
515 // running anymore, the panel will instantly hide itself. We need to wait until
516 // the animation is fully finished for this though.
517 changingView.performRemoveAnimation(ANIMATION_DURATION_HEADS_UP_DISAPPEAR
518 + ANIMATION_DELAY_HEADS_UP, extraDelay, 0.0f,
519 true /* isHeadsUpAppear */, targetLocation, endRunnable,
520 getGlobalAnimationFinishedListener());
521 } else if (endRunnable != null) {
522 endRunnable.run();
Selim Cinek8f937632015-06-05 15:22:42 +0200523 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200524 }
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200525 mNewEvents.add(event);
Selim Cinek572bbd42014-04-25 16:43:27 +0200526 }
527 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200528
Selim Cinek9dd0d042018-05-14 18:12:42 -0700529 public static void removeTransientView(ExpandableView viewToRemove) {
Rohan Shah524cf7b2018-03-15 14:40:02 -0700530 if (viewToRemove.getTransientContainer() != null) {
531 viewToRemove.getTransientContainer().removeTransientView(viewToRemove);
532 }
533 }
534
Jorim Jaggi475b21d2014-07-01 18:13:24 +0200535 public void animateOverScrollToAmount(float targetAmount, final boolean onTop,
536 final boolean isRubberbanded) {
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200537 final float startOverScrollAmount = mHostLayout.getCurrentOverScrollAmount(onTop);
Jorim Jaggi90129582014-06-02 14:44:49 +0200538 if (targetAmount == startOverScrollAmount) {
539 return;
540 }
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200541 cancelOverScrollAnimators(onTop);
542 ValueAnimator overScrollAnimator = ValueAnimator.ofFloat(startOverScrollAmount,
543 targetAmount);
544 overScrollAnimator.setDuration(ANIMATION_DURATION_STANDARD);
545 overScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
546 @Override
547 public void onAnimationUpdate(ValueAnimator animation) {
548 float currentOverScroll = (float) animation.getAnimatedValue();
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200549 mHostLayout.setOverScrollAmount(
Jorim Jaggi475b21d2014-07-01 18:13:24 +0200550 currentOverScroll, onTop, false /* animate */, false /* cancelAnimators */,
551 isRubberbanded);
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200552 }
553 });
Selim Cinekc18010f2016-01-20 13:41:30 -0800554 overScrollAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
Jorim Jaggi47c85a32014-06-05 17:25:40 +0200555 overScrollAnimator.addListener(new AnimatorListenerAdapter() {
556 @Override
557 public void onAnimationEnd(Animator animation) {
558 if (onTop) {
559 mTopOverScrollAnimator = null;
560 } else {
561 mBottomOverScrollAnimator = null;
562 }
563 }
564 });
Selim Cinek8d9ff9c2014-05-12 15:13:04 +0200565 overScrollAnimator.start();
566 if (onTop) {
567 mTopOverScrollAnimator = overScrollAnimator;
568 } else {
569 mBottomOverScrollAnimator = overScrollAnimator;
570 }
571 }
572
573 public void cancelOverScrollAnimators(boolean onTop) {
574 ValueAnimator currentAnimator = onTop ? mTopOverScrollAnimator : mBottomOverScrollAnimator;
575 if (currentAnimator != null) {
576 currentAnimator.cancel();
577 }
578 }
Selim Cinek02af41e2014-10-14 15:46:43 +0200579
Selim Cineka59ecc32015-04-07 10:51:49 -0700580 public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
581 mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
582 }
583
584 public void setShadeExpanded(boolean shadeExpanded) {
585 mShadeExpanded = shadeExpanded;
586 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700587
588 public void setShelf(NotificationShelf shelf) {
589 mShelf = shelf;
590 }
Selim Cinek572bbd42014-04-25 16:43:27 +0200591}