1 package com.android.systemui.statusbar.notification.stack 2 3 import androidx.core.view.children 4 import androidx.core.view.isVisible 5 import com.android.systemui.dagger.SysUISingleton 6 import com.android.systemui.flags.FeatureFlags 7 import com.android.systemui.flags.Flags 8 import com.android.systemui.statusbar.notification.Roundable 9 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow 10 import com.android.systemui.statusbar.notification.row.ExpandableView 11 import javax.inject.Inject 12 13 /** 14 * Utility class that helps us find the targets of an animation, often used to find the notification 15 * ([Roundable]) above and below the current one (see [findRoundableTargets]). 16 */ 17 @SysUISingleton 18 class NotificationTargetsHelper 19 @Inject 20 constructor( 21 featureFlags: FeatureFlags, 22 ) { 23 private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES) 24 25 /** 26 * This method looks for views that can be rounded (and implement [Roundable]) during a 27 * notification swipe. 28 * 29 * @return The [Roundable] targets above/below the [viewSwiped] (if available). The 30 * [RoundableTargets.before] and [RoundableTargets.after] parameters can be `null` if there is 31 * no above/below notification or the notification is not part of the same section. 32 */ findRoundableTargetsnull33 fun findRoundableTargets( 34 viewSwiped: ExpandableNotificationRow, 35 stackScrollLayout: NotificationStackScrollLayout, 36 sectionsManager: NotificationSectionsManager, 37 ): RoundableTargets { 38 val viewBefore: Roundable? 39 val viewAfter: Roundable? 40 41 val notificationParent = viewSwiped.notificationParent 42 val childrenContainer = notificationParent?.childrenContainer 43 val visibleStackChildren = 44 stackScrollLayout.children 45 .filterIsInstance<ExpandableView>() 46 .filter { it.isVisible } 47 .toList() 48 if (notificationParent != null && childrenContainer != null) { 49 // We are inside a notification group 50 51 if (!useRoundnessSourceTypes) { 52 return RoundableTargets(null, null, null) 53 } 54 55 val visibleGroupChildren = childrenContainer.attachedChildren.filter { it.isVisible } 56 val indexOfParentSwipedView = visibleGroupChildren.indexOf(viewSwiped) 57 58 viewBefore = 59 visibleGroupChildren.getOrNull(indexOfParentSwipedView - 1) 60 ?: childrenContainer.notificationHeaderWrapper 61 62 viewAfter = 63 visibleGroupChildren.getOrNull(indexOfParentSwipedView + 1) 64 ?: visibleStackChildren.indexOf(notificationParent).let { 65 visibleStackChildren.getOrNull(it + 1) 66 } 67 } else { 68 // Assumption: we are inside the NotificationStackScrollLayout 69 70 val indexOfSwipedView = visibleStackChildren.indexOf(viewSwiped) 71 72 viewBefore = 73 visibleStackChildren.getOrNull(indexOfSwipedView - 1)?.takeIf { 74 !sectionsManager.beginsSection(viewSwiped, it) 75 } 76 77 viewAfter = 78 visibleStackChildren.getOrNull(indexOfSwipedView + 1)?.takeIf { 79 !sectionsManager.beginsSection(it, viewSwiped) 80 } 81 } 82 83 return RoundableTargets( 84 before = viewBefore, 85 swiped = viewSwiped, 86 after = viewAfter, 87 ) 88 } 89 } 90 91 /** 92 * This object contains targets above/below the [swiped] (if available). The [before] and [after] 93 * parameters can be `null` if there is no above/below notification or the notification is not part 94 * of the same section. 95 */ 96 data class RoundableTargets( 97 val before: Roundable?, 98 val swiped: ExpandableNotificationRow?, 99 val after: Roundable?, 100 ) 101