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