• 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.NotificationStackScrollLayout.NUM_SECTIONS;
20 
21 import com.android.systemui.statusbar.AmbientPulseManager;
22 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
23 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
24 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
25 import com.android.systemui.statusbar.notification.row.ExpandableView;
26 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
27 
28 import java.util.HashSet;
29 
30 import javax.inject.Inject;
31 import javax.inject.Singleton;
32 
33 /**
34  * A class that manages the roundness for notification views
35  */
36 @Singleton
37 class NotificationRoundnessManager implements OnHeadsUpChangedListener,
38         AmbientPulseManager.OnAmbientChangedListener {
39 
40     private final ActivatableNotificationView[] mFirstInSectionViews;
41     private final ActivatableNotificationView[] mLastInSectionViews;
42     private final ActivatableNotificationView[] mTmpFirstInSectionViews;
43     private final ActivatableNotificationView[] mTmpLastInSectionViews;
44     private boolean mExpanded;
45     private HashSet<ExpandableView> mAnimatedChildren;
46     private Runnable mRoundingChangedCallback;
47     private ExpandableNotificationRow mTrackedHeadsUp;
48     private ActivatableNotificationView mTrackedAmbient;
49     private float mAppearFraction;
50 
51     @Inject
NotificationRoundnessManager(AmbientPulseManager ambientPulseManager)52     NotificationRoundnessManager(AmbientPulseManager ambientPulseManager) {
53         mFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
54         mLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
55         mTmpFirstInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
56         mTmpLastInSectionViews = new ActivatableNotificationView[NUM_SECTIONS];
57         ambientPulseManager.addListener(this);
58     }
59 
60     @Override
onHeadsUpPinned(NotificationEntry headsUp)61     public void onHeadsUpPinned(NotificationEntry headsUp) {
62         updateView(headsUp.getRow(), false /* animate */);
63     }
64 
65     @Override
onHeadsUpUnPinned(NotificationEntry headsUp)66     public void onHeadsUpUnPinned(NotificationEntry headsUp) {
67         updateView(headsUp.getRow(), true /* animate */);
68     }
69 
onHeadsupAnimatingAwayChanged(ExpandableNotificationRow row, boolean isAnimatingAway)70     public void onHeadsupAnimatingAwayChanged(ExpandableNotificationRow row,
71             boolean isAnimatingAway) {
72         updateView(row, false /* animate */);
73     }
74 
75     @Override
onAmbientStateChanged(NotificationEntry entry, boolean isPulsing)76     public void onAmbientStateChanged(NotificationEntry entry, boolean isPulsing) {
77         ActivatableNotificationView row = entry.getRow();
78         if (isPulsing) {
79             mTrackedAmbient = row;
80         } else if (mTrackedAmbient == row) {
81             mTrackedAmbient = null;
82         }
83         updateView(row, false /* animate */);
84     }
85 
updateView(ActivatableNotificationView view, boolean animate)86     private void updateView(ActivatableNotificationView view, boolean animate) {
87         boolean changed = updateViewWithoutCallback(view, animate);
88         if (changed) {
89             mRoundingChangedCallback.run();
90         }
91     }
92 
updateViewWithoutCallback(ActivatableNotificationView view, boolean animate)93     private boolean updateViewWithoutCallback(ActivatableNotificationView view,
94             boolean animate) {
95         float topRoundness = getRoundness(view, true /* top */);
96         float bottomRoundness = getRoundness(view, false /* top */);
97         boolean topChanged = view.setTopRoundness(topRoundness, animate);
98         boolean bottomChanged = view.setBottomRoundness(bottomRoundness, animate);
99         boolean firstInSection = isFirstInSection(view, false /* exclude first section */);
100         boolean lastInSection = isLastInSection(view, false /* exclude last section */);
101         view.setFirstInSection(firstInSection);
102         view.setLastInSection(lastInSection);
103         return (firstInSection || lastInSection) && (topChanged || bottomChanged);
104     }
105 
isFirstInSection(ActivatableNotificationView view, boolean includeFirstSection)106     private boolean isFirstInSection(ActivatableNotificationView view,
107             boolean includeFirstSection) {
108         int numNonEmptySections = 0;
109         for (int i = 0; i < mFirstInSectionViews.length; i++) {
110             if (view == mFirstInSectionViews[i]) {
111                 return includeFirstSection || numNonEmptySections > 0;
112             }
113             if (mFirstInSectionViews[i] != null) {
114                 numNonEmptySections++;
115             }
116         }
117         return false;
118     }
119 
isLastInSection(ActivatableNotificationView view, boolean includeLastSection)120     private boolean isLastInSection(ActivatableNotificationView view, boolean includeLastSection) {
121         int numNonEmptySections = 0;
122         for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
123             if (view == mLastInSectionViews[i]) {
124                 return includeLastSection || numNonEmptySections > 0;
125             }
126             if (mLastInSectionViews[i] != null) {
127                 numNonEmptySections++;
128             }
129         }
130         return false;
131     }
132 
getRoundness(ActivatableNotificationView view, boolean top)133     private float getRoundness(ActivatableNotificationView view, boolean top) {
134         if ((view.isPinned() || view.isHeadsUpAnimatingAway()) && !mExpanded) {
135             return 1.0f;
136         }
137         if (isFirstInSection(view, true /* include first section */) && top) {
138             return 1.0f;
139         }
140         if (isLastInSection(view, true /* include last section */) && !top) {
141             return 1.0f;
142         }
143         if (view == mTrackedHeadsUp && mAppearFraction <= 0.0f) {
144             // If we're pushing up on a headsup the appear fraction is < 0 and it needs to still be
145             // rounded.
146             return 1.0f;
147         }
148         if (view == mTrackedAmbient) {
149             return 1.0f;
150         }
151         return 0.0f;
152     }
153 
setExpanded(float expandedHeight, float appearFraction)154     public void setExpanded(float expandedHeight, float appearFraction) {
155         mExpanded = expandedHeight != 0.0f;
156         mAppearFraction = appearFraction;
157         if (mTrackedHeadsUp != null) {
158             updateView(mTrackedHeadsUp, true);
159         }
160     }
161 
updateRoundedChildren(NotificationSection[] sections)162     public void updateRoundedChildren(NotificationSection[] sections) {
163         boolean anyChanged = false;
164         for (int i = 0; i < NUM_SECTIONS; i++) {
165             mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
166             mTmpLastInSectionViews[i] = mLastInSectionViews[i];
167             mFirstInSectionViews[i] = sections[i].getFirstVisibleChild();
168             mLastInSectionViews[i] = sections[i].getLastVisibleChild();
169         }
170         anyChanged |= handleRemovedOldViews(sections, mTmpFirstInSectionViews, true);
171         anyChanged |= handleRemovedOldViews(sections, mTmpLastInSectionViews, false);
172         anyChanged |= handleAddedNewViews(sections, mTmpFirstInSectionViews, true);
173         anyChanged |= handleAddedNewViews(sections, mTmpLastInSectionViews, false);
174         if (anyChanged) {
175             mRoundingChangedCallback.run();
176         }
177     }
178 
handleRemovedOldViews(NotificationSection[] sections, ActivatableNotificationView[] oldViews, boolean first)179     private boolean handleRemovedOldViews(NotificationSection[] sections,
180             ActivatableNotificationView[] oldViews, boolean first) {
181         boolean anyChanged = false;
182         for (ActivatableNotificationView oldView : oldViews) {
183             if (oldView != null) {
184                 boolean isStillPresent = false;
185                 boolean adjacentSectionChanged = false;
186                 for (NotificationSection section : sections) {
187                     ActivatableNotificationView newView =
188                             (first ? section.getFirstVisibleChild()
189                                     : section.getLastVisibleChild());
190                     if (newView == oldView) {
191                         isStillPresent = true;
192                         if (oldView.isFirstInSection() != isFirstInSection(oldView,
193                                 false /* exclude first section */)
194                                 || oldView.isLastInSection() != isLastInSection(oldView,
195                                 false /* exclude last section */)) {
196                             adjacentSectionChanged = true;
197                         }
198                         break;
199                     }
200                 }
201                 if (!isStillPresent || adjacentSectionChanged) {
202                     anyChanged = true;
203                     if (!oldView.isRemoved()) {
204                         updateViewWithoutCallback(oldView, oldView.isShown());
205                     }
206                 }
207             }
208         }
209         return anyChanged;
210     }
211 
handleAddedNewViews(NotificationSection[] sections, ActivatableNotificationView[] oldViews, boolean first)212     private boolean handleAddedNewViews(NotificationSection[] sections,
213             ActivatableNotificationView[] oldViews, boolean first) {
214         boolean anyChanged = false;
215         for (NotificationSection section : sections) {
216             ActivatableNotificationView newView =
217                     (first ? section.getFirstVisibleChild() : section.getLastVisibleChild());
218             if (newView != null) {
219                 boolean wasAlreadyPresent = false;
220                 for (ActivatableNotificationView oldView : oldViews) {
221                     if (oldView == newView) {
222                         wasAlreadyPresent = true;
223                         break;
224                     }
225                 }
226                 if (!wasAlreadyPresent) {
227                     anyChanged = true;
228                     updateViewWithoutCallback(newView,
229                             newView.isShown() && !mAnimatedChildren.contains(newView));
230                 }
231             }
232         }
233         return anyChanged;
234     }
235 
setAnimatedChildren(HashSet<ExpandableView> animatedChildren)236     public void setAnimatedChildren(HashSet<ExpandableView> animatedChildren) {
237         mAnimatedChildren = animatedChildren;
238     }
239 
setOnRoundingChangedCallback(Runnable roundingChangedCallback)240     public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) {
241         mRoundingChangedCallback = roundingChangedCallback;
242     }
243 
setTrackingHeadsUp(ExpandableNotificationRow row)244     public void setTrackingHeadsUp(ExpandableNotificationRow row) {
245         mTrackedHeadsUp = row;
246     }
247 }
248