• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_MEDIA_CONTROLS;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.ObjectAnimator;
24 import android.animation.PropertyValuesHolder;
25 import android.graphics.Rect;
26 import android.view.View;
27 import android.view.animation.Interpolator;
28 
29 import com.android.systemui.animation.Interpolators;
30 import com.android.systemui.statusbar.notification.ShadeViewRefactor;
31 import com.android.systemui.statusbar.notification.row.ExpandableView;
32 
33 /**
34  * Represents the bounds of a section of the notification shade and handles animation when the
35  * bounds change.
36  */
37 public class NotificationSection {
38     private @PriorityBucket int mBucket;
39     private View mOwningView;
40     private Rect mBounds = new Rect();
41     private Rect mCurrentBounds = new Rect(-1, -1, -1, -1);
42     private Rect mStartAnimationRect = new Rect();
43     private Rect mEndAnimationRect = new Rect();
44     private ObjectAnimator mTopAnimator = null;
45     private ObjectAnimator mBottomAnimator = null;
46     private ExpandableView mFirstVisibleChild;
47     private ExpandableView mLastVisibleChild;
48 
NotificationSection(View owningView, @PriorityBucket int bucket)49     NotificationSection(View owningView, @PriorityBucket int bucket) {
50         mOwningView = owningView;
51         mBucket = bucket;
52     }
53 
cancelAnimators()54     public void cancelAnimators() {
55         if (mBottomAnimator != null) {
56             mBottomAnimator.cancel();
57         }
58         if (mTopAnimator != null) {
59             mTopAnimator.cancel();
60         }
61     }
62 
getCurrentBounds()63     public Rect getCurrentBounds() {
64         return mCurrentBounds;
65     }
66 
getBounds()67     public Rect getBounds() {
68         return mBounds;
69     }
70 
didBoundsChange()71     public boolean didBoundsChange() {
72         return !mCurrentBounds.equals(mBounds);
73     }
74 
areBoundsAnimating()75     public boolean areBoundsAnimating() {
76         return mBottomAnimator != null || mTopAnimator != null;
77     }
78 
79     @PriorityBucket
getBucket()80     public int getBucket() {
81         return mBucket;
82     }
83 
startBackgroundAnimation(boolean animateTop, boolean animateBottom)84     public void startBackgroundAnimation(boolean animateTop, boolean animateBottom) {
85         // Left and right bounds are always applied immediately.
86         mCurrentBounds.left = mBounds.left;
87         mCurrentBounds.right = mBounds.right;
88         startBottomAnimation(animateBottom);
89         startTopAnimation(animateTop);
90     }
91 
92 
93     @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.STATE_RESOLVER)
startTopAnimation(boolean animate)94     private void startTopAnimation(boolean animate) {
95         int previousEndValue = mEndAnimationRect.top;
96         int newEndValue = mBounds.top;
97         ObjectAnimator previousAnimator = mTopAnimator;
98         if (previousAnimator != null && previousEndValue == newEndValue) {
99             return;
100         }
101         if (!animate) {
102             // just a local update was performed
103             if (previousAnimator != null) {
104                 // we need to increase all animation keyframes of the previous animator by the
105                 // relative change to the end value
106                 int previousStartValue = mStartAnimationRect.top;
107                 PropertyValuesHolder[] values = previousAnimator.getValues();
108                 values[0].setIntValues(previousStartValue, newEndValue);
109                 mStartAnimationRect.top = previousStartValue;
110                 mEndAnimationRect.top = newEndValue;
111                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
112                 return;
113             } else {
114                 // no new animation needed, let's just apply the value
115                 setBackgroundTop(newEndValue);
116                 return;
117             }
118         }
119         if (previousAnimator != null) {
120             previousAnimator.cancel();
121         }
122         ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundTop",
123                 mCurrentBounds.top, newEndValue);
124         Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
125         animator.setInterpolator(interpolator);
126         animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
127         // remove the tag when the animation is finished
128         animator.addListener(new AnimatorListenerAdapter() {
129             @Override
130             public void onAnimationEnd(Animator animation) {
131                 mStartAnimationRect.top = -1;
132                 mEndAnimationRect.top = -1;
133                 mTopAnimator = null;
134             }
135         });
136         animator.start();
137         mStartAnimationRect.top = mCurrentBounds.top;
138         mEndAnimationRect.top = newEndValue;
139         mTopAnimator = animator;
140     }
141 
142     @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.STATE_RESOLVER)
startBottomAnimation(boolean animate)143     private void startBottomAnimation(boolean animate) {
144         int previousStartValue = mStartAnimationRect.bottom;
145         int previousEndValue = mEndAnimationRect.bottom;
146         int newEndValue = mBounds.bottom;
147         ObjectAnimator previousAnimator = mBottomAnimator;
148         if (previousAnimator != null && previousEndValue == newEndValue) {
149             return;
150         }
151         if (!animate) {
152             // just a local update was performed
153             if (previousAnimator != null) {
154                 // we need to increase all animation keyframes of the previous animator by the
155                 // relative change to the end value
156                 PropertyValuesHolder[] values = previousAnimator.getValues();
157                 values[0].setIntValues(previousStartValue, newEndValue);
158                 mStartAnimationRect.bottom = previousStartValue;
159                 mEndAnimationRect.bottom = newEndValue;
160                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
161                 return;
162             } else {
163                 // no new animation needed, let's just apply the value
164                 setBackgroundBottom(newEndValue);
165                 return;
166             }
167         }
168         if (previousAnimator != null) {
169             previousAnimator.cancel();
170         }
171         ObjectAnimator animator = ObjectAnimator.ofInt(this, "backgroundBottom",
172                 mCurrentBounds.bottom, newEndValue);
173         Interpolator interpolator = Interpolators.FAST_OUT_SLOW_IN;
174         animator.setInterpolator(interpolator);
175         animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
176         // remove the tag when the animation is finished
177         animator.addListener(new AnimatorListenerAdapter() {
178             @Override
179             public void onAnimationEnd(Animator animation) {
180                 mStartAnimationRect.bottom = -1;
181                 mEndAnimationRect.bottom = -1;
182                 mBottomAnimator = null;
183             }
184         });
185         animator.start();
186         mStartAnimationRect.bottom = mCurrentBounds.bottom;
187         mEndAnimationRect.bottom = newEndValue;
188         mBottomAnimator = animator;
189     }
190 
191     @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.SHADE_VIEW)
setBackgroundTop(int top)192     private void setBackgroundTop(int top) {
193         mCurrentBounds.top = top;
194         mOwningView.invalidate();
195     }
196 
197     @ShadeViewRefactor(ShadeViewRefactor.RefactorComponent.SHADE_VIEW)
setBackgroundBottom(int bottom)198     private void setBackgroundBottom(int bottom) {
199         mCurrentBounds.bottom = bottom;
200         mOwningView.invalidate();
201     }
202 
getFirstVisibleChild()203     public ExpandableView getFirstVisibleChild() {
204         return mFirstVisibleChild;
205     }
206 
getLastVisibleChild()207     public ExpandableView getLastVisibleChild() {
208         return mLastVisibleChild;
209     }
210 
setFirstVisibleChild(ExpandableView child)211     public boolean setFirstVisibleChild(ExpandableView child) {
212         boolean changed = mFirstVisibleChild != child;
213         mFirstVisibleChild = child;
214         return changed;
215     }
216 
setLastVisibleChild(ExpandableView child)217     public boolean setLastVisibleChild(ExpandableView child) {
218         boolean changed = mLastVisibleChild != child;
219         mLastVisibleChild = child;
220         return changed;
221     }
222 
resetCurrentBounds()223     public void resetCurrentBounds() {
224         mCurrentBounds.set(mBounds);
225     }
226 
227     /**
228      * Returns true if {@code top} is equal to the top of this section (if not currently animating)
229      * or where the top of this section will be when animation completes.
230      */
isTargetTop(int top)231     public boolean isTargetTop(int top) {
232         return (mTopAnimator == null && mCurrentBounds.top == top)
233                 || (mTopAnimator != null && mEndAnimationRect.top == top);
234     }
235 
236     /**
237      * Returns true if {@code bottom} is equal to the bottom of this section (if not currently
238      * animating) or where the bottom of this section will be when animation completes.
239      */
isTargetBottom(int bottom)240     public boolean isTargetBottom(int bottom) {
241         return (mBottomAnimator == null && mCurrentBounds.bottom == bottom)
242                 || (mBottomAnimator != null && mEndAnimationRect.bottom == bottom);
243     }
244 
245     /**
246      * Update the bounds of this section based on it's views
247      *
248      * @param minTopPosition the minimum position that the top needs to have
249      * @param minBottomPosition the minimum position that the bottom needs to have
250      * @return the position of the new bottom
251      */
updateBounds(int minTopPosition, int minBottomPosition, boolean shiftBackgroundWithFirst)252     public int updateBounds(int minTopPosition, int minBottomPosition,
253             boolean shiftBackgroundWithFirst) {
254         int top = minTopPosition;
255         int bottom = minTopPosition;
256         ExpandableView firstView = getFirstVisibleChild();
257         if (firstView != null) {
258             // Round Y up to avoid seeing the background during animation
259             int finalTranslationY = (int) Math.ceil(ViewState.getFinalTranslationY(firstView));
260             // TODO: look into the already animating part
261             int newTop;
262             if (isTargetTop(finalTranslationY)) {
263                 // we're ending up at the same location as we are now, let's just skip the
264                 // animation
265                 newTop = finalTranslationY;
266             } else {
267                 newTop = (int) Math.ceil(firstView.getTranslationY());
268             }
269             top = Math.max(newTop, top);
270             if (firstView.showingPulsing()) {
271                 // If we're pulsing, the notification can actually go below!
272                 bottom = Math.max(bottom, finalTranslationY
273                         + ExpandableViewState.getFinalActualHeight(firstView));
274                 if (shiftBackgroundWithFirst) {
275                     mBounds.left += Math.max(firstView.getTranslation(), 0);
276                     mBounds.right += Math.min(firstView.getTranslation(), 0);
277                 }
278             }
279         }
280         top = Math.max(minTopPosition, top);
281         ExpandableView lastView = getLastVisibleChild();
282         if (lastView != null) {
283             float finalTranslationY = ViewState.getFinalTranslationY(lastView);
284             int finalHeight = ExpandableViewState.getFinalActualHeight(lastView);
285             // Round Y down to avoid seeing the background during animation
286             int finalBottom = (int) Math.floor(
287                     finalTranslationY + finalHeight - lastView.getClipBottomAmount());
288             int newBottom;
289             if (isTargetBottom(finalBottom)) {
290                 // we're ending up at the same location as we are now, lets just skip the animation
291                 newBottom = finalBottom;
292             } else {
293                 newBottom = (int) (lastView.getTranslationY() + lastView.getActualHeight()
294                         - lastView.getClipBottomAmount());
295                 // The background can never be lower than the end of the last view
296                 minBottomPosition = (int) Math.min(
297                         lastView.getTranslationY() + lastView.getActualHeight(),
298                         minBottomPosition);
299             }
300             bottom = Math.max(bottom, Math.max(newBottom, minBottomPosition));
301         }
302         bottom = Math.max(top, bottom);
303         mBounds.top = top;
304         mBounds.bottom = bottom;
305         return bottom;
306     }
307 
needsBackground()308     public boolean needsBackground() {
309         return mFirstVisibleChild != null && mBucket != BUCKET_MEDIA_CONTROLS;
310     }
311 }
312