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