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