• 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 android.content.res.Resources;
20 import android.util.MathUtils;
21 
22 import com.android.systemui.R;
23 import com.android.systemui.dagger.SysUISingleton;
24 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
25 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
26 import com.android.systemui.statusbar.notification.row.ExpandableView;
27 import com.android.systemui.statusbar.phone.KeyguardBypassController;
28 
29 import java.util.HashSet;
30 
31 import javax.inject.Inject;
32 
33 /**
34  * A class that manages the roundness for notification views
35  */
36 @SysUISingleton
37 public class NotificationRoundnessManager {
38 
39     private final ExpandableView[] mFirstInSectionViews;
40     private final ExpandableView[] mLastInSectionViews;
41     private final ExpandableView[] mTmpFirstInSectionViews;
42     private final ExpandableView[] mTmpLastInSectionViews;
43     private final KeyguardBypassController mBypassController;
44     private boolean mExpanded;
45     private HashSet<ExpandableView> mAnimatedChildren;
46     private Runnable mRoundingChangedCallback;
47     private ExpandableNotificationRow mTrackedHeadsUp;
48     private float mAppearFraction;
49 
50     private ExpandableView mSwipedView = null;
51     private ExpandableView mViewBeforeSwipedView = null;
52     private ExpandableView mViewAfterSwipedView = null;
53 
54     @Inject
NotificationRoundnessManager( KeyguardBypassController keyguardBypassController, NotificationSectionsFeatureManager sectionsFeatureManager)55     NotificationRoundnessManager(
56             KeyguardBypassController keyguardBypassController,
57             NotificationSectionsFeatureManager sectionsFeatureManager) {
58         int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
59         mFirstInSectionViews = new ExpandableView[numberOfSections];
60         mLastInSectionViews = new ExpandableView[numberOfSections];
61         mTmpFirstInSectionViews = new ExpandableView[numberOfSections];
62         mTmpLastInSectionViews = new ExpandableView[numberOfSections];
63         mBypassController = keyguardBypassController;
64     }
65 
updateView(ExpandableView view, boolean animate)66     public void updateView(ExpandableView view, boolean animate) {
67         boolean changed = updateViewWithoutCallback(view, animate);
68         if (changed) {
69             mRoundingChangedCallback.run();
70         }
71     }
72 
isViewAffectedBySwipe(ExpandableView expandableView)73     public boolean isViewAffectedBySwipe(ExpandableView expandableView) {
74         return expandableView != null
75                 && (expandableView == mSwipedView
76                     || expandableView == mViewBeforeSwipedView
77                     || expandableView == mViewAfterSwipedView);
78     }
79 
updateViewWithoutCallback(ExpandableView view, boolean animate)80     boolean updateViewWithoutCallback(ExpandableView view,
81             boolean animate) {
82         if (view == null
83                 || view == mViewBeforeSwipedView
84                 || view == mViewAfterSwipedView) {
85             return false;
86         }
87 
88         final float topRoundness = getRoundness(view, true /* top */);
89         final float bottomRoundness = getRoundness(view, false /* top */);
90 
91         final boolean topChanged = view.setTopRoundness(topRoundness, animate);
92         final boolean bottomChanged = view.setBottomRoundness(bottomRoundness, animate);
93 
94         final boolean isFirstInSection = isFirstInSection(view);
95         final boolean isLastInSection = isLastInSection(view);
96 
97         view.setFirstInSection(isFirstInSection);
98         view.setLastInSection(isLastInSection);
99 
100         return (isFirstInSection || isLastInSection) && (topChanged || bottomChanged);
101     }
102 
isFirstInSection(ExpandableView view)103     private boolean isFirstInSection(ExpandableView view) {
104         for (int i = 0; i < mFirstInSectionViews.length; i++) {
105             if (view == mFirstInSectionViews[i]) {
106                 return true;
107             }
108         }
109         return false;
110     }
111 
isLastInSection(ExpandableView view)112     private boolean isLastInSection(ExpandableView view) {
113         for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
114             if (view == mLastInSectionViews[i]) {
115                 return true;
116             }
117         }
118         return false;
119     }
120 
setViewsAffectedBySwipe( ExpandableView viewBefore, ExpandableView viewSwiped, ExpandableView viewAfter, boolean cornerAnimationsEnabled)121     void setViewsAffectedBySwipe(
122             ExpandableView viewBefore,
123             ExpandableView viewSwiped,
124             ExpandableView viewAfter,
125             boolean cornerAnimationsEnabled) {
126         if (!cornerAnimationsEnabled) {
127             return;
128         }
129         final boolean animate = true;
130 
131         ExpandableView oldViewBefore = mViewBeforeSwipedView;
132         mViewBeforeSwipedView = viewBefore;
133         if (oldViewBefore != null) {
134             final float bottomRoundness = getRoundness(oldViewBefore, false /* top */);
135             oldViewBefore.setBottomRoundness(bottomRoundness,  animate);
136         }
137         if (viewBefore != null) {
138             viewBefore.setBottomRoundness(1f, animate);
139         }
140 
141         ExpandableView oldSwipedview = mSwipedView;
142         mSwipedView = viewSwiped;
143         if (oldSwipedview != null) {
144             final float bottomRoundness = getRoundness(oldSwipedview, false /* top */);
145             final float topRoundness = getRoundness(oldSwipedview, true /* top */);
146             oldSwipedview.setTopRoundness(topRoundness, animate);
147             oldSwipedview.setBottomRoundness(bottomRoundness, animate);
148         }
149         if (viewSwiped != null) {
150             viewSwiped.setTopRoundness(1f, animate);
151             viewSwiped.setBottomRoundness(1f, animate);
152         }
153 
154         ExpandableView oldViewAfter = mViewAfterSwipedView;
155         mViewAfterSwipedView = viewAfter;
156         if (oldViewAfter != null) {
157             final float topRoundness = getRoundness(oldViewAfter, true /* top */);
158             oldViewAfter.setTopRoundness(topRoundness, animate);
159         }
160         if (viewAfter != null) {
161             viewAfter.setTopRoundness(1f, animate);
162         }
163     }
164 
getRoundness(ExpandableView view, boolean top)165     private float getRoundness(ExpandableView view, boolean top) {
166         if (view == null) {
167             return 0f;
168         }
169         if (view == mViewBeforeSwipedView
170                 || view == mSwipedView
171                 || view == mViewAfterSwipedView) {
172             return 1f;
173         }
174         if ((view.isPinned()
175                 || (view.isHeadsUpAnimatingAway()) && !mExpanded)) {
176             return 1.0f;
177         }
178         if (isFirstInSection(view) && top) {
179             return 1.0f;
180         }
181         if (isLastInSection(view) && !top) {
182             return 1.0f;
183         }
184         if (view == mTrackedHeadsUp) {
185             // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be
186             // rounded.
187             return MathUtils.saturate(1.0f - mAppearFraction);
188         }
189         if (view.showingPulsing() && !mBypassController.getBypassEnabled()) {
190             return 1.0f;
191         }
192         final Resources resources = view.getResources();
193         return resources.getDimension(R.dimen.notification_corner_radius_small)
194                 / resources.getDimension(R.dimen.notification_corner_radius);
195     }
196 
setExpanded(float expandedHeight, float appearFraction)197     public void setExpanded(float expandedHeight, float appearFraction) {
198         mExpanded = expandedHeight != 0.0f;
199         mAppearFraction = appearFraction;
200         if (mTrackedHeadsUp != null) {
201             updateView(mTrackedHeadsUp, false /* animate */);
202         }
203     }
204 
updateRoundedChildren(NotificationSection[] sections)205     public void updateRoundedChildren(NotificationSection[] sections) {
206         boolean anyChanged = false;
207         for (int i = 0; i < sections.length; i++) {
208             mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
209             mTmpLastInSectionViews[i] = mLastInSectionViews[i];
210             mFirstInSectionViews[i] = sections[i].getFirstVisibleChild();
211             mLastInSectionViews[i] = sections[i].getLastVisibleChild();
212         }
213         anyChanged |= handleRemovedOldViews(sections, mTmpFirstInSectionViews, true);
214         anyChanged |= handleRemovedOldViews(sections, mTmpLastInSectionViews, false);
215         anyChanged |= handleAddedNewViews(sections, mTmpFirstInSectionViews, true);
216         anyChanged |= handleAddedNewViews(sections, mTmpLastInSectionViews, false);
217         if (anyChanged) {
218             mRoundingChangedCallback.run();
219         }
220     }
221 
handleRemovedOldViews(NotificationSection[] sections, ExpandableView[] oldViews, boolean first)222     private boolean handleRemovedOldViews(NotificationSection[] sections,
223             ExpandableView[] oldViews, boolean first) {
224         boolean anyChanged = false;
225         for (ExpandableView oldView : oldViews) {
226             if (oldView != null) {
227                 boolean isStillPresent = false;
228                 boolean adjacentSectionChanged = false;
229                 for (NotificationSection section : sections) {
230                     ExpandableView newView =
231                             (first ? section.getFirstVisibleChild()
232                                     : section.getLastVisibleChild());
233                     if (newView == oldView) {
234                         isStillPresent = true;
235                         if (oldView.isFirstInSection() != isFirstInSection(oldView)
236                                 || oldView.isLastInSection() != isLastInSection(oldView)) {
237                             adjacentSectionChanged = true;
238                         }
239                         break;
240                     }
241                 }
242                 if (!isStillPresent || adjacentSectionChanged) {
243                     anyChanged = true;
244                     if (!oldView.isRemoved()) {
245                         updateViewWithoutCallback(oldView, oldView.isShown());
246                     }
247                 }
248             }
249         }
250         return anyChanged;
251     }
252 
handleAddedNewViews(NotificationSection[] sections, ExpandableView[] oldViews, boolean first)253     private boolean handleAddedNewViews(NotificationSection[] sections,
254             ExpandableView[] oldViews, boolean first) {
255         boolean anyChanged = false;
256         for (NotificationSection section : sections) {
257             ExpandableView newView =
258                     (first ? section.getFirstVisibleChild() : section.getLastVisibleChild());
259             if (newView != null) {
260                 boolean wasAlreadyPresent = false;
261                 for (ExpandableView oldView : oldViews) {
262                     if (oldView == newView) {
263                         wasAlreadyPresent = true;
264                         break;
265                     }
266                 }
267                 if (!wasAlreadyPresent) {
268                     anyChanged = true;
269                     updateViewWithoutCallback(newView,
270                             newView.isShown() && !mAnimatedChildren.contains(newView));
271                 }
272             }
273         }
274         return anyChanged;
275     }
276 
setAnimatedChildren(HashSet<ExpandableView> animatedChildren)277     public void setAnimatedChildren(HashSet<ExpandableView> animatedChildren) {
278         mAnimatedChildren = animatedChildren;
279     }
280 
setOnRoundingChangedCallback(Runnable roundingChangedCallback)281     public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) {
282         mRoundingChangedCallback = roundingChangedCallback;
283     }
284 
setTrackingHeadsUp(ExpandableNotificationRow row)285     public void setTrackingHeadsUp(ExpandableNotificationRow row) {
286         ExpandableNotificationRow previous = mTrackedHeadsUp;
287         mTrackedHeadsUp = row;
288         if (previous != null) {
289             updateView(previous, true /* animate */);
290         }
291     }
292 }
293