blob: 72ef7f9572a4b29c2b9a5690fb538d0ce62c9b8b [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
Rohan Shah20790b82018-07-02 17:21:04 -070017package com.android.systemui.statusbar.notification.stack;
Selim Cinekbbcebde2016-11-09 18:28:20 -080018
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;
Rohan Shah20790b82018-07-02 17:21:04 -070027import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
28import com.android.systemui.statusbar.notification.row.ExpandableView;
Selim Cinekbbcebde2016-11-09 18:28:20 -080029
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;
Selim Cinek0cfbef42016-11-09 19:06:36 -080037 private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
38 private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
Selim Cinek0cfbef42016-11-09 19:06:36 -080039 private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
40 private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
Selim Cinek0cfbef42016-11-09 19:06:36 -080041
Selim Cinekbbcebde2016-11-09 18:28:20 -080042 // These are flags such that we can create masks for filtering.
43
44 /**
45 * No known location. This is the default and should not be set after an invocation of the
46 * algorithm.
47 */
48 public static final int LOCATION_UNKNOWN = 0x00;
49
50 /**
51 * The location is the first heads up notification, so on the very top.
52 */
53 public static final int LOCATION_FIRST_HUN = 0x01;
54
55 /**
56 * The location is hidden / scrolled away on the top.
57 */
58 public static final int LOCATION_HIDDEN_TOP = 0x02;
59
60 /**
61 * The location is in the main area of the screen and visible.
62 */
63 public static final int LOCATION_MAIN_AREA = 0x04;
64
65 /**
66 * The location is in the bottom stack and it's peeking
67 */
68 public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08;
69
70 /**
71 * The location is in the bottom stack and it's hidden.
72 */
73 public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
74
75 /**
Selim Cinek281c2022016-10-13 19:14:43 -070076 * The view isn't laid out at all.
77 */
Selim Cinekbbcebde2016-11-09 18:28:20 -080078 public static final int LOCATION_GONE = 0x40;
79
Selim Cineka7d4f822016-12-06 14:34:47 -080080 /**
81 * The visible locations of a view.
82 */
83 public static final int VISIBLE_LOCATIONS = ExpandableViewState.LOCATION_FIRST_HUN
84 | ExpandableViewState.LOCATION_MAIN_AREA;
85
Selim Cinekbbcebde2016-11-09 18:28:20 -080086 public int height;
87 public boolean dimmed;
Selim Cinekbbcebde2016-11-09 18:28:20 -080088 public boolean hideSensitive;
Selim Cinekdb167372016-11-17 15:41:17 -080089 public boolean belowSpeedBump;
Selim Cinekeccb5de2016-10-28 15:04:05 -070090 public boolean inShelf;
Selim Cinekbbcebde2016-11-09 18:28:20 -080091
92 /**
Selim Cinek9b9d6e12017-11-30 12:29:47 +010093 * A state indicating whether a headsup is currently fully visible, even when not scrolled.
94 * Only valid if the view is heads upped.
95 */
96 public boolean headsUpIsVisible;
97
98 /**
Selim Cinekbbcebde2016-11-09 18:28:20 -080099 * How much the child overlaps with the previous child on top. This is used to
100 * show the background properly when the child on top is translating away.
101 */
102 public int clipTopAmount;
103
104 /**
105 * The index of the view, only accounting for views not equal to GONE
106 */
107 public int notGoneIndex;
108
109 /**
110 * The location this view is currently rendered at.
111 *
112 * <p>See <code>LOCATION_</code> flags.</p>
113 */
114 public int location;
115
Selim Cinekbbcebde2016-11-09 18:28:20 -0800116 @Override
117 public void copyFrom(ViewState viewState) {
118 super.copyFrom(viewState);
119 if (viewState instanceof ExpandableViewState) {
120 ExpandableViewState svs = (ExpandableViewState) viewState;
121 height = svs.height;
122 dimmed = svs.dimmed;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800123 hideSensitive = svs.hideSensitive;
Selim Cinekdb167372016-11-17 15:41:17 -0800124 belowSpeedBump = svs.belowSpeedBump;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800125 clipTopAmount = svs.clipTopAmount;
126 notGoneIndex = svs.notGoneIndex;
127 location = svs.location;
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100128 headsUpIsVisible = svs.headsUpIsVisible;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800129 }
130 }
131
132 /**
133 * Applies a {@link ExpandableViewState} to a {@link ExpandableView}.
134 */
135 @Override
136 public void applyToView(View view) {
137 super.applyToView(view);
138 if (view instanceof ExpandableView) {
139 ExpandableView expandableView = (ExpandableView) view;
140
141 int height = expandableView.getActualHeight();
142 int newHeight = this.height;
143
144 // apply height
145 if (height != newHeight) {
146 expandableView.setActualHeight(newHeight, false /* notifyListeners */);
147 }
148
Selim Cinekbbcebde2016-11-09 18:28:20 -0800149 // apply dimming
150 expandableView.setDimmed(this.dimmed, false /* animate */);
151
152 // apply hiding sensitive
153 expandableView.setHideSensitive(
154 this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
155
Selim Cinekdb167372016-11-17 15:41:17 -0800156 // apply below shelf speed bump
157 expandableView.setBelowSpeedBump(this.belowSpeedBump);
Selim Cinekbbcebde2016-11-09 18:28:20 -0800158
Selim Cinekbbcebde2016-11-09 18:28:20 -0800159 // apply clipping
160 float oldClipTopAmount = expandableView.getClipTopAmount();
161 if (oldClipTopAmount != this.clipTopAmount) {
162 expandableView.setClipTopAmount(this.clipTopAmount);
163 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700164
165 expandableView.setTransformingInShelf(false);
166 expandableView.setInShelf(inShelf);
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100167
168 if (headsUpIsVisible) {
169 expandableView.setHeadsUpIsVisible();
170 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800171 }
172 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800173
174 @Override
175 public void animateTo(View child, AnimationProperties properties) {
176 super.animateTo(child, properties);
177 if (!(child instanceof ExpandableView)) {
178 return;
179 }
180 ExpandableView expandableView = (ExpandableView) child;
181 AnimationFilter animationFilter = properties.getAnimationFilter();
182
183 // start height animation
184 if (this.height != expandableView.getActualHeight()) {
185 startHeightAnimation(expandableView, properties);
186 } else {
187 abortAnimation(child, TAG_ANIMATOR_HEIGHT);
188 }
189
Selim Cinek0cfbef42016-11-09 19:06:36 -0800190 // start top inset animation
191 if (this.clipTopAmount != expandableView.getClipTopAmount()) {
192 startInsetAnimation(expandableView, properties);
193 } else {
194 abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
195 }
196
197 // start dimmed animation
198 expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
199
Selim Cinekdb167372016-11-17 15:41:17 -0800200 // apply below the speed bump
201 expandableView.setBelowSpeedBump(this.belowSpeedBump);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800202
203 // start hiding sensitive animation
204 expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
205 properties.delay, properties.duration);
206
Selim Cinek5b5beb012016-11-08 18:11:58 -0800207 if (properties.wasAdded(child) && !hidden) {
Selim Cinek332c23f2018-03-16 17:37:50 -0700208 expandableView.performAddAnimation(properties.delay, properties.duration,
209 false /* isHeadsUpAppear */);
Selim Cinek0cfbef42016-11-09 19:06:36 -0800210 }
Selim Cinekeccb5de2016-10-28 15:04:05 -0700211
212 if (!expandableView.isInShelf() && this.inShelf) {
213 expandableView.setTransformingInShelf(true);
214 }
215 expandableView.setInShelf(this.inShelf);
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100216
217 if (headsUpIsVisible) {
218 expandableView.setHeadsUpIsVisible();
219 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800220 }
221
222 private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) {
223 Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
224 Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
225 int newEndValue = this.height;
226 if (previousEndValue != null && previousEndValue == newEndValue) {
227 return;
228 }
229 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
230 AnimationFilter filter = properties.getAnimationFilter();
231 if (!filter.animateHeight) {
232 // just a local update was performed
233 if (previousAnimator != null) {
234 // we need to increase all animation keyframes of the previous animator by the
235 // relative change to the end value
236 PropertyValuesHolder[] values = previousAnimator.getValues();
237 int relativeDiff = newEndValue - previousEndValue;
238 int newStartValue = previousStartValue + relativeDiff;
239 values[0].setIntValues(newStartValue, newEndValue);
240 child.setTag(TAG_START_HEIGHT, newStartValue);
241 child.setTag(TAG_END_HEIGHT, newEndValue);
242 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
243 return;
244 } else {
245 // no new animation needed, let's just apply the value
246 child.setActualHeight(newEndValue, false);
247 return;
248 }
249 }
250
251 ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
252 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
253 @Override
254 public void onAnimationUpdate(ValueAnimator animation) {
255 child.setActualHeight((int) animation.getAnimatedValue(),
256 false /* notifyListeners */);
257 }
258 });
259 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
260 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
261 animator.setDuration(newDuration);
262 if (properties.delay > 0 && (previousAnimator == null
263 || previousAnimator.getAnimatedFraction() == 0)) {
264 animator.setStartDelay(properties.delay);
265 }
266 AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
267 if (listener != null) {
268 animator.addListener(listener);
269 }
270 // remove the tag when the animation is finished
271 animator.addListener(new AnimatorListenerAdapter() {
272 boolean mWasCancelled;
273
274 @Override
275 public void onAnimationEnd(Animator animation) {
276 child.setTag(TAG_ANIMATOR_HEIGHT, null);
277 child.setTag(TAG_START_HEIGHT, null);
278 child.setTag(TAG_END_HEIGHT, null);
279 child.setActualHeightAnimating(false);
280 if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
281 ((ExpandableNotificationRow) child).setGroupExpansionChanging(
282 false /* isExpansionChanging */);
283 }
284 }
285
286 @Override
287 public void onAnimationStart(Animator animation) {
288 mWasCancelled = false;
289 }
290
291 @Override
292 public void onAnimationCancel(Animator animation) {
293 mWasCancelled = true;
294 }
295 });
296 startAnimator(animator, listener);
297 child.setTag(TAG_ANIMATOR_HEIGHT, animator);
298 child.setTag(TAG_START_HEIGHT, child.getActualHeight());
299 child.setTag(TAG_END_HEIGHT, newEndValue);
300 child.setActualHeightAnimating(true);
301 }
302
Selim Cinek0cfbef42016-11-09 19:06:36 -0800303 private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
304 Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
305 Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
306 int newEndValue = this.clipTopAmount;
307 if (previousEndValue != null && previousEndValue == newEndValue) {
308 return;
309 }
310 ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
311 AnimationFilter filter = properties.getAnimationFilter();
312 if (!filter.animateTopInset) {
313 // just a local update was performed
314 if (previousAnimator != null) {
315 // we need to increase all animation keyframes of the previous animator by the
316 // relative change to the end value
317 PropertyValuesHolder[] values = previousAnimator.getValues();
318 int relativeDiff = newEndValue - previousEndValue;
319 int newStartValue = previousStartValue + relativeDiff;
320 values[0].setIntValues(newStartValue, newEndValue);
321 child.setTag(TAG_START_TOP_INSET, newStartValue);
322 child.setTag(TAG_END_TOP_INSET, newEndValue);
323 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
324 return;
325 } else {
326 // no new animation needed, let's just apply the value
327 child.setClipTopAmount(newEndValue);
328 return;
329 }
330 }
331
332 ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
333 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
334 @Override
335 public void onAnimationUpdate(ValueAnimator animation) {
336 child.setClipTopAmount((int) animation.getAnimatedValue());
337 }
338 });
339 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
340 long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
341 animator.setDuration(newDuration);
342 if (properties.delay > 0 && (previousAnimator == null
343 || previousAnimator.getAnimatedFraction() == 0)) {
344 animator.setStartDelay(properties.delay);
345 }
346 AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
347 if (listener != null) {
348 animator.addListener(listener);
349 }
350 // remove the tag when the animation is finished
351 animator.addListener(new AnimatorListenerAdapter() {
352 @Override
353 public void onAnimationEnd(Animator animation) {
354 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
355 child.setTag(TAG_START_TOP_INSET, null);
356 child.setTag(TAG_END_TOP_INSET, null);
357 }
358 });
359 startAnimator(animator, listener);
360 child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
361 child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
362 child.setTag(TAG_END_TOP_INSET, newEndValue);
363 }
364
365 /**
366 * Get the end value of the height animation running on a view or the actualHeight
367 * if no animation is running.
368 */
369 public static int getFinalActualHeight(ExpandableView view) {
370 if (view == null) {
371 return 0;
372 }
373 ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
374 if (heightAnimator == null) {
375 return view.getActualHeight();
376 } else {
377 return getChildTag(view, TAG_END_HEIGHT);
378 }
379 }
Selim Cinek2627d722018-01-19 12:16:49 -0800380
381 @Override
382 public void cancelAnimations(View view) {
383 super.cancelAnimations(view);
384 Animator animator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
385 if (animator != null) {
386 animator.cancel();
387 }
Selim Cinek2627d722018-01-19 12:16:49 -0800388 animator = getChildTag(view, TAG_ANIMATOR_TOP_INSET);
389 if (animator != null) {
390 animator.cancel();
391 }
392 }
Selim Cinekbbcebde2016-11-09 18:28:20 -0800393}