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