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