• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.systemui.statusbar.notification.stack;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.PropertyValuesHolder;
22 import android.animation.ValueAnimator;
23 import android.view.View;
24 
25 import com.android.systemui.Interpolators;
26 import com.android.systemui.R;
27 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
28 import com.android.systemui.statusbar.notification.row.ExpandableView;
29 
30 /**
31 * A state of an expandable view
32 */
33 public class ExpandableViewState extends ViewState {
34 
35     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_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;
39     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;
41 
42     // 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     /**
76      * The view isn't laid out at all.
77      */
78     public static final int LOCATION_GONE = 0x40;
79 
80     /**
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 
86     public int height;
87     public boolean dimmed;
88     public boolean dark;
89     public boolean hideSensitive;
90     public boolean belowSpeedBump;
91     public boolean inShelf;
92 
93     /**
94      * A state indicating whether a headsup is currently fully visible, even when not scrolled.
95      * Only valid if the view is heads upped.
96      */
97     public boolean headsUpIsVisible;
98 
99     /**
100      * How much the child overlaps with the previous child on top. This is used to
101      * show the background properly when the child on top is translating away.
102      */
103     public int clipTopAmount;
104 
105     /**
106      * The index of the view, only accounting for views not equal to GONE
107      */
108     public int notGoneIndex;
109 
110     /**
111      * The location this view is currently rendered at.
112      *
113      * <p>See <code>LOCATION_</code> flags.</p>
114      */
115     public int location;
116 
117     @Override
copyFrom(ViewState viewState)118     public void copyFrom(ViewState viewState) {
119         super.copyFrom(viewState);
120         if (viewState instanceof ExpandableViewState) {
121             ExpandableViewState svs = (ExpandableViewState) viewState;
122             height = svs.height;
123             dimmed = svs.dimmed;
124             dark = svs.dark;
125             hideSensitive = svs.hideSensitive;
126             belowSpeedBump = svs.belowSpeedBump;
127             clipTopAmount = svs.clipTopAmount;
128             notGoneIndex = svs.notGoneIndex;
129             location = svs.location;
130             headsUpIsVisible = svs.headsUpIsVisible;
131         }
132     }
133 
134     /**
135      * Applies a {@link ExpandableViewState} to a {@link ExpandableView}.
136      */
137     @Override
applyToView(View view)138     public void applyToView(View view) {
139         super.applyToView(view);
140         if (view instanceof ExpandableView) {
141             ExpandableView expandableView = (ExpandableView) view;
142 
143             int height = expandableView.getActualHeight();
144             int newHeight = this.height;
145 
146             // apply height
147             if (height != newHeight) {
148                 expandableView.setActualHeight(newHeight, false /* notifyListeners */);
149             }
150 
151             // apply dimming
152             expandableView.setDimmed(this.dimmed, false /* animate */);
153 
154             // apply hiding sensitive
155             expandableView.setHideSensitive(
156                     this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
157 
158             // apply below shelf speed bump
159             expandableView.setBelowSpeedBump(this.belowSpeedBump);
160 
161             // apply dark
162             expandableView.setDark(this.dark, false /* animate */, 0 /* delay */);
163 
164             // apply clipping
165             float oldClipTopAmount = expandableView.getClipTopAmount();
166             if (oldClipTopAmount != this.clipTopAmount) {
167                 expandableView.setClipTopAmount(this.clipTopAmount);
168             }
169 
170             expandableView.setTransformingInShelf(false);
171             expandableView.setInShelf(inShelf);
172 
173             if (headsUpIsVisible) {
174                 expandableView.setHeadsUpIsVisible();
175             }
176         }
177     }
178 
179     @Override
animateTo(View child, AnimationProperties properties)180     public void animateTo(View child, AnimationProperties properties) {
181         super.animateTo(child, properties);
182         if (!(child instanceof ExpandableView)) {
183             return;
184         }
185         ExpandableView expandableView = (ExpandableView) child;
186         AnimationFilter animationFilter = properties.getAnimationFilter();
187 
188         // start height animation
189         if (this.height != expandableView.getActualHeight()) {
190             startHeightAnimation(expandableView, properties);
191         }  else {
192             abortAnimation(child, TAG_ANIMATOR_HEIGHT);
193         }
194 
195         // start top inset animation
196         if (this.clipTopAmount != expandableView.getClipTopAmount()) {
197             startInsetAnimation(expandableView, properties);
198         } else {
199             abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
200         }
201 
202         // start dimmed animation
203         expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
204 
205         // apply below the speed bump
206         expandableView.setBelowSpeedBump(this.belowSpeedBump);
207 
208         // start hiding sensitive animation
209         expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
210                 properties.delay, properties.duration);
211 
212         // start dark animation
213         expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay);
214 
215         if (properties.wasAdded(child) && !hidden) {
216             expandableView.performAddAnimation(properties.delay, properties.duration,
217                     false /* isHeadsUpAppear */);
218         }
219 
220         if (!expandableView.isInShelf() && this.inShelf) {
221             expandableView.setTransformingInShelf(true);
222         }
223         expandableView.setInShelf(this.inShelf);
224 
225         if (headsUpIsVisible) {
226             expandableView.setHeadsUpIsVisible();
227         }
228     }
229 
startHeightAnimation(final ExpandableView child, AnimationProperties properties)230     private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) {
231         Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
232         Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
233         int newEndValue = this.height;
234         if (previousEndValue != null && previousEndValue == newEndValue) {
235             return;
236         }
237         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
238         AnimationFilter filter = properties.getAnimationFilter();
239         if (!filter.animateHeight) {
240             // just a local update was performed
241             if (previousAnimator != null) {
242                 // we need to increase all animation keyframes of the previous animator by the
243                 // relative change to the end value
244                 PropertyValuesHolder[] values = previousAnimator.getValues();
245                 int relativeDiff = newEndValue - previousEndValue;
246                 int newStartValue = previousStartValue + relativeDiff;
247                 values[0].setIntValues(newStartValue, newEndValue);
248                 child.setTag(TAG_START_HEIGHT, newStartValue);
249                 child.setTag(TAG_END_HEIGHT, newEndValue);
250                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
251                 return;
252             } else {
253                 // no new animation needed, let's just apply the value
254                 child.setActualHeight(newEndValue, false);
255                 return;
256             }
257         }
258 
259         ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
260         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
261             @Override
262             public void onAnimationUpdate(ValueAnimator animation) {
263                 child.setActualHeight((int) animation.getAnimatedValue(),
264                         false /* notifyListeners */);
265             }
266         });
267         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
268         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
269         animator.setDuration(newDuration);
270         if (properties.delay > 0 && (previousAnimator == null
271                 || previousAnimator.getAnimatedFraction() == 0)) {
272             animator.setStartDelay(properties.delay);
273         }
274         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
275         if (listener != null) {
276             animator.addListener(listener);
277         }
278         // remove the tag when the animation is finished
279         animator.addListener(new AnimatorListenerAdapter() {
280             boolean mWasCancelled;
281 
282             @Override
283             public void onAnimationEnd(Animator animation) {
284                 child.setTag(TAG_ANIMATOR_HEIGHT, null);
285                 child.setTag(TAG_START_HEIGHT, null);
286                 child.setTag(TAG_END_HEIGHT, null);
287                 child.setActualHeightAnimating(false);
288                 if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
289                     ((ExpandableNotificationRow) child).setGroupExpansionChanging(
290                             false /* isExpansionChanging */);
291                 }
292             }
293 
294             @Override
295             public void onAnimationStart(Animator animation) {
296                 mWasCancelled = false;
297             }
298 
299             @Override
300             public void onAnimationCancel(Animator animation) {
301                 mWasCancelled = true;
302             }
303         });
304         startAnimator(animator, listener);
305         child.setTag(TAG_ANIMATOR_HEIGHT, animator);
306         child.setTag(TAG_START_HEIGHT, child.getActualHeight());
307         child.setTag(TAG_END_HEIGHT, newEndValue);
308         child.setActualHeightAnimating(true);
309     }
310 
startInsetAnimation(final ExpandableView child, AnimationProperties properties)311     private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
312         Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
313         Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
314         int newEndValue = this.clipTopAmount;
315         if (previousEndValue != null && previousEndValue == newEndValue) {
316             return;
317         }
318         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
319         AnimationFilter filter = properties.getAnimationFilter();
320         if (!filter.animateTopInset) {
321             // just a local update was performed
322             if (previousAnimator != null) {
323                 // we need to increase all animation keyframes of the previous animator by the
324                 // relative change to the end value
325                 PropertyValuesHolder[] values = previousAnimator.getValues();
326                 int relativeDiff = newEndValue - previousEndValue;
327                 int newStartValue = previousStartValue + relativeDiff;
328                 values[0].setIntValues(newStartValue, newEndValue);
329                 child.setTag(TAG_START_TOP_INSET, newStartValue);
330                 child.setTag(TAG_END_TOP_INSET, newEndValue);
331                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
332                 return;
333             } else {
334                 // no new animation needed, let's just apply the value
335                 child.setClipTopAmount(newEndValue);
336                 return;
337             }
338         }
339 
340         ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
341         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
342             @Override
343             public void onAnimationUpdate(ValueAnimator animation) {
344                 child.setClipTopAmount((int) animation.getAnimatedValue());
345             }
346         });
347         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
348         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
349         animator.setDuration(newDuration);
350         if (properties.delay > 0 && (previousAnimator == null
351                 || previousAnimator.getAnimatedFraction() == 0)) {
352             animator.setStartDelay(properties.delay);
353         }
354         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
355         if (listener != null) {
356             animator.addListener(listener);
357         }
358         // remove the tag when the animation is finished
359         animator.addListener(new AnimatorListenerAdapter() {
360             @Override
361             public void onAnimationEnd(Animator animation) {
362                 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
363                 child.setTag(TAG_START_TOP_INSET, null);
364                 child.setTag(TAG_END_TOP_INSET, null);
365             }
366         });
367         startAnimator(animator, listener);
368         child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
369         child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
370         child.setTag(TAG_END_TOP_INSET, newEndValue);
371     }
372 
373     /**
374      * Get the end value of the height animation running on a view or the actualHeight
375      * if no animation is running.
376      */
getFinalActualHeight(ExpandableView view)377     public static int getFinalActualHeight(ExpandableView view) {
378         if (view == null) {
379             return 0;
380         }
381         ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
382         if (heightAnimator == null) {
383             return view.getActualHeight();
384         } else {
385             return getChildTag(view, TAG_END_HEIGHT);
386         }
387     }
388 
389     @Override
cancelAnimations(View view)390     public void cancelAnimations(View view) {
391         super.cancelAnimations(view);
392         Animator animator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
393         if (animator != null) {
394             animator.cancel();
395         }
396         animator = getChildTag(view, TAG_ANIMATOR_TOP_INSET);
397         if (animator != null) {
398             animator.cancel();
399         }
400     }
401 }
402