• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
20 
21 /**
22  * An interface to coordinate the magnetic behavior of notifications when swiped.
23  *
24  * During swiping a notification, this manager receives calls to set the horizontal translation of
25  * the notification and events that indicate that the interaction with the notification ended ( see
26  * the documentation for [onMagneticInteractionEnd]). The latter represent events when the
27  * notification is swiped out by dragging or flinging, or when it snaps back when the view is
28  * released from the gesture.
29  *
30  * This manager uses all of these inputs to implement a magnetic attachment between the notification
31  * swiped and its neighbors, as well as a detaching moment after crossing a threshold.
32  */
33 interface MagneticNotificationRowManager {
34 
35     /**
36      * Notifies a change in the device density. The density can be used to compute the values of
37      * thresholds in pixels.
38      *
39      * @param[density] The device density.
40      */
onDensityChangenull41     fun onDensityChange(density: Float)
42 
43     /**
44      * Set the magnetic and roundable targets of a magnetic swipe interaction.
45      *
46      * This method should construct and set a list of [MagneticRowListener] objects and a
47      * [RoundableTargets] object when an [ExpandableNotificationRow] starts to be swiped. The
48      * magnetic targets interact magnetically as the [expandableNotificationRow] is swiped, and the
49      * [RoundableTargets] get rounded when the row detaches from its magnetic couplings.
50      *
51      * This method must be called when the [swipingRow] starts to be swiped. It represents the
52      * beginning of the magnetic swipe.
53      *
54      * @param[swipingRow] The [ExpandableNotificationRow] that is being swiped. This is the main
55      *   magnetic element that pulls its neighbors as it is swiped.
56      * @param[stackScrollLayout] The [NotificationStackScrollLayout] that contains notifications.
57      * @param[sectionsManager] The [NotificationSectionsManager] that helps identify roundable
58      *   targets.
59      */
60     fun setMagneticAndRoundableTargets(
61         swipingRow: ExpandableNotificationRow,
62         stackScrollLayout: NotificationStackScrollLayout,
63         sectionsManager: NotificationSectionsManager,
64     )
65 
66     /**
67      * Set the translation of an [ExpandableNotificationRow].
68      *
69      * This method must be called after [setMagneticAndRoundableTargets] has been called and must be
70      * called for each movement on the [row] being that is being swiped.
71      *
72      * @return true if the given [row] is the current magnetic view being swiped by the user, false
73      *   otherwise. If false, no translation is applied and this method has no effect.
74      */
75     fun setMagneticRowTranslation(row: ExpandableNotificationRow, translation: Float): Boolean
76 
77     /**
78      * Notifies that the magnetic interactions with the [ExpandableNotificationRow] stopped.
79      *
80      * This occurs if the row stopped being swiped and will snap back, or if it was ultimately
81      * dismissed. This method represents the end of the magnetic interaction and must be called
82      * after calls to [setMagneticRowTranslation].
83      *
84      * @param[row] [ExpandableNotificationRow] that stopped whose interaction stopped.
85      * @param[velocity] Optional velocity at the end of the interaction. Use this to trigger
86      *   animations with a start velocity.
87      */
88     fun onMagneticInteractionEnd(row: ExpandableNotificationRow, velocity: Float? = null)
89 
90     /**
91      * Determine if a magnetic row swiped is dismissible according to the end velocity of the swipe.
92      */
93     fun isMagneticRowSwipedDismissible(row: ExpandableNotificationRow, endVelocity: Float): Boolean
94 
95     /* Reset any roundness that magnetic targets may have */
96     fun resetRoundness()
97 
98     /**
99      * Reset any magnetic and roundable targets set, as well as any internal state.
100      *
101      * This method is in charge of proper cleanup by cancelling animations, clearing targets and
102      * resetting any internal state in the implementation. One use case of this method is when
103      * notifications must be cleared in the middle of a magnetic interaction and
104      * [onMagneticInteractionEnd] will not be called from the lifecycle of the user gesture.
105      */
106     fun reset()
107 
108     companion object {
109         /** Detaching threshold in dp */
110         const val MAGNETIC_DETACH_THRESHOLD_DP = 56
111 
112         /** Re-attaching threshold in dp */
113         const val MAGNETIC_ATTACH_THRESHOLD_DP = 40
114 
115         /* An empty implementation of a manager */
116         @JvmStatic
117         val Empty: MagneticNotificationRowManager
118             get() =
119                 object : MagneticNotificationRowManager {
120                     override fun onDensityChange(density: Float) {}
121 
122                     override fun setMagneticAndRoundableTargets(
123                         swipingRow: ExpandableNotificationRow,
124                         stackScrollLayout: NotificationStackScrollLayout,
125                         sectionsManager: NotificationSectionsManager,
126                     ) {}
127 
128                     override fun setMagneticRowTranslation(
129                         row: ExpandableNotificationRow,
130                         translation: Float,
131                     ): Boolean = false
132 
133                     override fun onMagneticInteractionEnd(
134                         row: ExpandableNotificationRow,
135                         velocity: Float?,
136                     ) {}
137 
138                     override fun isMagneticRowSwipedDismissible(
139                         row: ExpandableNotificationRow,
140                         endVelocity: Float,
141                     ): Boolean = false
142 
143                     override fun resetRoundness() {}
144 
145                     override fun reset() {}
146                 }
147     }
148 }
149