blob: 3bf7d892ea0e87dacf256e47379fda176bd402d1 [file] [log] [blame]
Selim Cinekbbcebde2016-11-09 18:28:20 -08001/*
2 * Copyright (C) 2015 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 Cinek0cfbef42016-11-09 19:06:36 -080019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.PropertyValuesHolder;
22import android.animation.ValueAnimator;
Selim Cinekbbcebde2016-11-09 18:28:20 -080023import android.view.View;
24
Selim Cinek0cfbef42016-11-09 19:06:36 -080025import com.android.systemui.Interpolators;
26import com.android.systemui.R;
27import com.android.systemui.statusbar.ExpandableNotificationRow;
Selim Cinekbbcebde2016-11-09 18:28:20 -080028import com.android.systemui.statusbar.ExpandableView;
29
30/**
31* A state of an expandable view
32*/
33public class ExpandableViewState extends ViewState {
34
Selim Cinek0cfbef42016-11-09 19:06:36 -080035 private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
36 private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
37 private static final int TAG_ANIMATOR_SHADOW_ALPHA = R.id.shadow_alpha_animator_tag;
38 private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
39 private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
40 private static final int TAG_END_SHADOW_ALPHA = R.id.shadow_alpha_animator_end_value_tag;
41 private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
42 private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
43 private static final int TAG_START_SHADOW_ALPHA = R.id.shadow_alpha_animator_start_value_tag;
44
Selim Cinekbbcebde2016-11-09 18:28:20 -080045 // These are flags such that we can create masks for filtering.
46
47 /**
48 * No known location. This is the default and should not be set after an invocation of the
49 * algorithm.
50 */
51 public static final int LOCATION_UNKNOWN = 0x00;
52
53 /**
54 * The location is the first heads up notification, so on the very top.
55 */
56 public static final int LOCATION_FIRST_HUN = 0x01;
57
58 /**
59 * The location is hidden / scrolled away on the top.
60 */
61 public static final int LOCATION_HIDDEN_TOP = 0x02;
62
63 /**
64 * The location is in the main area of the screen and visible.
65 */
66 public static final int LOCATION_MAIN_AREA = 0x04;
67
68 /**
69 * The location is in the bottom stack and it's peeking
70 */
71 public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08;
72
73 /**
74 * The location is in the bottom stack and it's hidden.
75 */
76 public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
77
78 /**
Selim Cinek281c2022016-10-13 19:14:43 -070079 * The view isn't laid out at all.
80 */
Selim Cinekbbcebde2016-11-09 18:28:20 -080081 public static final int LOCATION_GONE = 0x40;
82
Selim Cineka7d4f822016-12-06 14:34:47 -080083 /**
84 * The visible locations of a view.
85 */
86 public static final int VISIBLE_LOCATIONS = ExpandableViewState.LOCATION_FIRST_HUN
87 | ExpandableViewState.LOCATION_MAIN_AREA;
88
Selim Cinekbbcebde2016-11-09 18:28:20 -080089 public int height;
90 public boolean dimmed;
91 public boolean dark;
92 public boolean hideSensitive;
Selim Cinekdb167372016-11-17 15:41:17 -080093 public boolean belowSpeedBump;
Selim Cinekbbcebde2016-11-09 18:28:20 -080094 public float shadowAlpha;
Selim Cinekeccb5de2016-10-28 15:04:05 -070095 public boolean inShelf;
Selim Cinekbbcebde2016-11-09 18:28:20 -080096
97 /**
Selim Cinek9b9d6e12017-11-30 12:29:47 +010098 * A state indicating whether a headsup is currently fully visible, even when not scrolled.
99 * Only valid if the view is heads upped.
100 */
101 public boolean headsUpIsVisible;
102
103 /**
Selim Cinekbbcebde2016-11-09 18:28:20 -0800104 * How much the child overlaps with the previous child on top. This is used to
105 * show the background properly when the child on top is translating away.
106 */
107 public int clipTopAmount;
108
109 /**
110 * The index of the view, only accounting for views not equal to GONE
111 */
112 public int notGoneIndex;
113
114 /**
115 * The location this view is currently rendered at.
116 *
117 * <p>See <code>LOCATION_</code> flags.</p>
118 */
119 public int location;
120
Selim Cinekbbcebde2016-11-09 18:28:20 -0800121 @Override
122 public void copyFrom(ViewState viewState) {
123 super.copyFrom(viewState);
124 if (viewState instanceof ExpandableViewState) {
125 ExpandableViewState svs = (ExpandableViewState) viewState;
126 height = svs.height;
127 dimmed = svs.dimmed;
128 shadowAlpha = svs.shadowAlpha;
129 dark = svs.dark;
130 hideSensitive = svs.hideSensitive;
Selim Cinekdb167372016-11-17 15:41:17 -0800131 belowSpeedBump = svs.belowSpeedBump;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800132 clipTopAmount = svs.clipTopAmount;
133 notGoneIndex = svs.notGoneIndex;
134 location = svs.location;
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100135 headsUpIsVisible = svs.headsUpIsVisible;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800136 }
137 }
138
139 /**
140 * Applies a {@link ExpandableViewState} to a {@link ExpandableView}.
141 */
142 @Override
143 public void applyToView(View view) {
144 super.applyToView(view);
145 if (view instanceof ExpandableView) {
146 ExpandableView expandableView = (ExpandableView) view;
147
148 int height = expandableView.getActualHeight();
149 int newHeight = this.height;
150
151 // apply height
152 if (height != newHeight) {
153 expandableView.setActualHeight(newHeight, false /* notifyListeners */);
154 }
155
156 float shadowAlpha = expandableView.getShadowAlpha();
157 float newShadowAlpha = this.shadowAlpha;
158
159 // apply shadowAlpha
160 if (shadowAlpha != newShadowAlpha) {
161 expandableView.setShadowAlpha(newShadowAlpha);
162 }
163
164 // apply dimming
165 expandableView.setDimmed(this.dimmed, false /* animate */);
166
167 // apply hiding sensitive
168 expandableView.setHideSensitive(
169 this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
170
Selim Cinekdb167372016-11-17 15:41:17 -0800171 // apply below shelf speed bump
172 expandableView.setBelowSpeedBump(this.belowSpeedBump);
Selim Cinekbbcebde2016-11-09 18:28:20 -0800173
174 // apply dark
175 expandableView.setDark(this.dark, false /* animate */, 0 /* delay */);
176
177 // apply clipping
178 float oldClipTopAmount = expandableView.getClipTopAmount();
179 if (oldClipTopAmount != this.clipTopAmount) {
180 expandableView.setClipTopAmount(this.clipTopAmount);
181 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700182
183 expandableView.setTransformingInShelf(false);
184 expandableView.setInShelf(inShelf);
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100185
186 if (headsUpIsVisible) {
187 expandableView.setHeadsUpIsVisible();
188 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800189 }
190 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800191
192 @Override
193 public void animateTo(View child, AnimationProperties properties) {
194 super.animateTo(child, properties);
195 if (!(child instanceof ExpandableView)) {
196 return;
197 }
198 ExpandableView expandableView = (ExpandableView) child;
199 AnimationFilter animationFilter = properties.getAnimationFilter();
200
201 // start height animation
202 if (this.height != expandableView.getActualHeight()) {
203 startHeightAnimation(expandableView, properties);
204 } else {
205 abortAnimation(child, TAG_ANIMATOR_HEIGHT);
206 }
207
208 // start shadow alpha animation
209 if (this.shadowAlpha != expandableView.getShadowAlpha()) {
210 startShadowAlphaAnimation(expandableView, properties);
211 } else {
212 abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA);
213 }
214
215 // start top inset animation
216 if (this.clipTopAmount != expandableView.getClipTopAmount()) {
217 startInsetAnimation(expandableView, properties);
218 } else {
219 abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
220 }
221
222 // start dimmed animation
223 expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
224
Selim Cinekdb167372016-11-17 15:41:17 -0800225 // apply below the speed bump
226 expandableView.setBelowSpeedBump(this.belowSpeedBump);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800227
228 // start hiding sensitive animation
229 expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
230 properties.delay, properties.duration);
231
232 // start dark animation
233 expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay);
234
Selim Cinek5b5beb012016-11-08 18:11:58 -0800235 if (properties.wasAdded(child) && !hidden) {
Selim Cinek0cfbef42016-11-09 19:06:36 -0800236 expandableView.performAddAnimation(properties.delay, properties.duration);
237 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700238
239 if (!expandableView.isInShelf() && this.inShelf) {
240 expandableView.setTransformingInShelf(true);
241 }
242 expandableView.setInShelf(this.inShelf);
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100243
244 if (headsUpIsVisible) {
245 expandableView.setHeadsUpIsVisible();
246 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800247 }
248
249 private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) {
250 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
251 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
252 int newEndValue = this.height;
253 if (previousEndValue != null && previousEndValue == newEndValue) {
254 return;
255 }
256 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
257 AnimationFilter filter = properties.getAnimationFilter();
258 if (!filter.animateHeight) {
259 // just a local update was performed
260 if (previousAnimator != null) {
261 // we need to increase all animation keyframes of the previous animator by the
262 // relative change to the end value
263 PropertyValuesHolder[] values = previousAnimator.getValues();
264 int relativeDiff = newEndValue - previousEndValue;
265 int newStartValue = previousStartValue + relativeDiff;
266 values[0].setIntValues(newStartValue, newEndValue);
267 child.setTag(TAG_START_HEIGHT, newStartValue);
268 child.setTag(TAG_END_HEIGHT, newEndValue);
269 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
270 return;
271 } else {
272 // no new animation needed, let's just apply the value
273 child.setActualHeight(newEndValue, false);
274 return;
275 }
276 }
277
278 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
279 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
280 @Override
281 public void onAnimationUpdate(ValueAnimator animation) {
282 child.setActualHeight((int) animation.getAnimatedValue(),
283 false /* notifyListeners */);
284 }
285 });
286 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
287 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
288 animator.setDuration(newDuration);
289 if (properties.delay > 0 && (previousAnimator == null
290 || previousAnimator.getAnimatedFraction() == 0)) {
291 animator.setStartDelay(properties.delay);
292 }
293 AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
294 if (listener != null) {
295 animator.addListener(listener);
296 }
297 // remove the tag when the animation is finished
298 animator.addListener(new AnimatorListenerAdapter() {
299 boolean mWasCancelled;
300
301 @Override
302 public void onAnimationEnd(Animator animation) {
303 child.setTag(TAG_ANIMATOR_HEIGHT, null);
304 child.setTag(TAG_START_HEIGHT, null);
305 child.setTag(TAG_END_HEIGHT, null);
306 child.setActualHeightAnimating(false);
307 if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
308 ((ExpandableNotificationRow) child).setGroupExpansionChanging(
309 false /* isExpansionChanging */);
310 }
311 }
312
313 @Override
314 public void onAnimationStart(Animator animation) {
315 mWasCancelled = false;
316 }
317
318 @Override
319 public void onAnimationCancel(Animator animation) {
320 mWasCancelled = true;
321 }
322 });
323 startAnimator(animator, listener);
324 child.setTag(TAG_ANIMATOR_HEIGHT, animator);
325 child.setTag(TAG_START_HEIGHT, child.getActualHeight());
326 child.setTag(TAG_END_HEIGHT, newEndValue);
327 child.setActualHeightAnimating(true);
328 }
329
330 private void startShadowAlphaAnimation(final ExpandableView child,
331 AnimationProperties properties) {
332 Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA);
333 Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA);
334 float newEndValue = this.shadowAlpha;
335 if (previousEndValue != null && previousEndValue == newEndValue) {
336 return;
337 }
338 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA);
339 AnimationFilter filter = properties.getAnimationFilter();
340 if (!filter.animateShadowAlpha) {
341 // just a local update was performed
342 if (previousAnimator != null) {
343 // we need to increase all animation keyframes of the previous animator by the
344 // relative change to the end value
345 PropertyValuesHolder[] values = previousAnimator.getValues();
346 float relativeDiff = newEndValue - previousEndValue;
347 float newStartValue = previousStartValue + relativeDiff;
348 values[0].setFloatValues(newStartValue, newEndValue);
349 child.setTag(TAG_START_SHADOW_ALPHA, newStartValue);
350 child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
351 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
352 return;
353 } else {
354 // no new animation needed, let's just apply the value
355 child.setShadowAlpha(newEndValue);
356 return;
357 }
358 }
359
360 ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue);
361 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
362 @Override
363 public void onAnimationUpdate(ValueAnimator animation) {
364 child.setShadowAlpha((float) animation.getAnimatedValue());
365 }
366 });
367 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
368 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
369 animator.setDuration(newDuration);
370 if (properties.delay > 0 && (previousAnimator == null
371 || previousAnimator.getAnimatedFraction() == 0)) {
372 animator.setStartDelay(properties.delay);
373 }
374 AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
375 if (listener != null) {
376 animator.addListener(listener);
377 }
378 // remove the tag when the animation is finished
379 animator.addListener(new AnimatorListenerAdapter() {
380 @Override
381 public void onAnimationEnd(Animator animation) {
382 child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null);
383 child.setTag(TAG_START_SHADOW_ALPHA, null);
384 child.setTag(TAG_END_SHADOW_ALPHA, null);
385 }
386 });
387 startAnimator(animator, listener);
388 child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator);
389 child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha());
390 child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
391 }
392
393 private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
394 Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
395 Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
396 int newEndValue = this.clipTopAmount;
397 if (previousEndValue != null && previousEndValue == newEndValue) {
398 return;
399 }
400 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
401 AnimationFilter filter = properties.getAnimationFilter();
402 if (!filter.animateTopInset) {
403 // just a local update was performed
404 if (previousAnimator != null) {
405 // we need to increase all animation keyframes of the previous animator by the
406 // relative change to the end value
407 PropertyValuesHolder[] values = previousAnimator.getValues();
408 int relativeDiff = newEndValue - previousEndValue;
409 int newStartValue = previousStartValue + relativeDiff;
410 values[0].setIntValues(newStartValue, newEndValue);
411 child.setTag(TAG_START_TOP_INSET, newStartValue);
412 child.setTag(TAG_END_TOP_INSET, newEndValue);
413 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
414 return;
415 } else {
416 // no new animation needed, let's just apply the value
417 child.setClipTopAmount(newEndValue);
418 return;
419 }
420 }
421
422 ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
423 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
424 @Override
425 public void onAnimationUpdate(ValueAnimator animation) {
426 child.setClipTopAmount((int) animation.getAnimatedValue());
427 }
428 });
429 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
430 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
431 animator.setDuration(newDuration);
432 if (properties.delay > 0 && (previousAnimator == null
433 || previousAnimator.getAnimatedFraction() == 0)) {
434 animator.setStartDelay(properties.delay);
435 }
436 AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
437 if (listener != null) {
438 animator.addListener(listener);
439 }
440 // remove the tag when the animation is finished
441 animator.addListener(new AnimatorListenerAdapter() {
442 @Override
443 public void onAnimationEnd(Animator animation) {
444 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
445 child.setTag(TAG_START_TOP_INSET, null);
446 child.setTag(TAG_END_TOP_INSET, null);
447 }
448 });
449 startAnimator(animator, listener);
450 child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
451 child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
452 child.setTag(TAG_END_TOP_INSET, newEndValue);
453 }
454
455 /**
456 * Get the end value of the height animation running on a view or the actualHeight
457 * if no animation is running.
458 */
459 public static int getFinalActualHeight(ExpandableView view) {
460 if (view == null) {
461 return 0;
462 }
463 ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
464 if (heightAnimator == null) {
465 return view.getActualHeight();
466 } else {
467 return getChildTag(view, TAG_END_HEIGHT);
468 }
469 }
Selim Cinek2627d722018-01-19 12:16:49 -0800470
471 @Override
472 public void cancelAnimations(View view) {
473 super.cancelAnimations(view);
474 Animator animator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
475 if (animator != null) {
476 animator.cancel();
477 }
478 animator = getChildTag(view, TAG_ANIMATOR_SHADOW_ALPHA);
479 if (animator != null) {
480 animator.cancel();
481 }
482 animator = getChildTag(view, TAG_ANIMATOR_TOP_INSET);
483 if (animator != null) {
484 animator.cancel();
485 }
486 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800487}