• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.systemui.statusbar.notification
2 
3 import android.view.ViewGroup
4 import com.android.internal.jank.InteractionJankMonitor
5 import com.android.systemui.animation.ActivityLaunchAnimator
6 import com.android.systemui.animation.LaunchAnimator
7 import com.android.systemui.shade.NotificationShadeWindowViewController
8 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
9 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
10 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
11 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
12 import com.android.systemui.statusbar.policy.HeadsUpUtil
13 import javax.inject.Inject
14 import kotlin.math.ceil
15 import kotlin.math.max
16 
17 /** A provider of [NotificationLaunchAnimatorController]. */
18 @CentralSurfacesComponent.CentralSurfacesScope
19 class NotificationLaunchAnimatorControllerProvider @Inject constructor(
20     private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
21     private val notificationListContainer: NotificationListContainer,
22     private val headsUpManager: HeadsUpManagerPhone,
23     private val jankMonitor: InteractionJankMonitor
24 ) {
25     @JvmOverloads
getAnimatorControllernull26     fun getAnimatorController(
27         notification: ExpandableNotificationRow,
28         onFinishAnimationCallback: Runnable? = null
29     ): NotificationLaunchAnimatorController {
30         return NotificationLaunchAnimatorController(
31             notificationShadeWindowViewController,
32             notificationListContainer,
33             headsUpManager,
34             notification,
35             jankMonitor,
36             onFinishAnimationCallback
37         )
38     }
39 }
40 
41 /**
42  * An [ActivityLaunchAnimator.Controller] that animates an [ExpandableNotificationRow]. An instance
43  * of this class can be passed to [ActivityLaunchAnimator.startIntentWithAnimation] to animate a
44  * notification expanding into an opening window.
45  */
46 class NotificationLaunchAnimatorController(
47     private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
48     private val notificationListContainer: NotificationListContainer,
49     private val headsUpManager: HeadsUpManagerPhone,
50     private val notification: ExpandableNotificationRow,
51     private val jankMonitor: InteractionJankMonitor,
52     private val onFinishAnimationCallback: Runnable?
53 ) : ActivityLaunchAnimator.Controller {
54 
55     companion object {
56         const val ANIMATION_DURATION_TOP_ROUNDING = 100L
57     }
58 
59     private val notificationEntry = notification.entry
60     private val notificationKey = notificationEntry.sbn.key
61 
62     override var launchContainer: ViewGroup
63         get() = notification.rootView as ViewGroup
64         set(ignored) {
65             // Do nothing. Notifications are always animated inside their rootView.
66         }
67 
createAnimatorStatenull68     override fun createAnimatorState(): LaunchAnimator.State {
69         // If the notification panel is collapsed, the clip may be larger than the height.
70         val height = max(0, notification.actualHeight - notification.clipBottomAmount)
71         val location = notification.locationOnScreen
72 
73         val clipStartLocation = notificationListContainer.topClippingStartLocation
74         val roundedTopClipping = (clipStartLocation - location[1]).coerceAtLeast(0)
75         val windowTop = location[1] + roundedTopClipping
76         val topCornerRadius = if (roundedTopClipping > 0) {
77             // Because the rounded Rect clipping is complex, we start the top rounding at
78             // 0, which is pretty close to matching the real clipping.
79             // We'd have to clipOut the overlaid drawable too with the outer rounded rect in case
80             // if we'd like to have this perfect, but this is close enough.
81             0f
82         } else {
83             notification.topCornerRadius
84         }
85         val params = LaunchAnimationParameters(
86             top = windowTop,
87             bottom = location[1] + height,
88             left = location[0],
89             right = location[0] + notification.width,
90             topCornerRadius = topCornerRadius,
91             bottomCornerRadius = notification.bottomCornerRadius
92         )
93 
94         params.startTranslationZ = notification.translationZ
95         params.startNotificationTop = location[1]
96         params.notificationParentTop = notificationListContainer
97                 .getViewParentForNotification(notificationEntry).locationOnScreen[1]
98         params.startRoundedTopClipping = roundedTopClipping
99         params.startClipTopAmount = notification.clipTopAmount
100         if (notification.isChildInGroup) {
101             val locationOnScreen = notification.notificationParent.locationOnScreen[1]
102             val parentRoundedClip = (clipStartLocation - locationOnScreen).coerceAtLeast(0)
103             params.parentStartRoundedTopClipping = parentRoundedClip
104 
105             val parentClip = notification.notificationParent.clipTopAmount
106             params.parentStartClipTopAmount = parentClip
107 
108             // We need to calculate how much the child is clipped by the parent because children
109             // always have 0 clipTopAmount
110             if (parentClip != 0) {
111                 val childClip = parentClip - notification.translationY
112                 if (childClip > 0) {
113                     params.startClipTopAmount = ceil(childClip.toDouble()).toInt()
114                 }
115             }
116         }
117 
118         return params
119     }
120 
onIntentStartednull121     override fun onIntentStarted(willAnimate: Boolean) {
122         notificationShadeWindowViewController.setExpandAnimationRunning(willAnimate)
123         notificationEntry.isExpandAnimationRunning = willAnimate
124 
125         if (!willAnimate) {
126             removeHun(animate = true)
127             onFinishAnimationCallback?.run()
128         }
129     }
130 
removeHunnull131     private fun removeHun(animate: Boolean) {
132         if (!headsUpManager.isAlerting(notificationKey)) {
133             return
134         }
135 
136         HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(notification, animate)
137         headsUpManager.removeNotification(notificationKey, true /* releaseImmediately */, animate)
138     }
139 
onLaunchAnimationCancellednull140     override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
141         // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
142         // here?
143         notificationShadeWindowViewController.setExpandAnimationRunning(false)
144         notificationEntry.isExpandAnimationRunning = false
145         removeHun(animate = true)
146         onFinishAnimationCallback?.run()
147     }
148 
onLaunchAnimationStartnull149     override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
150         notification.isExpandAnimationRunning = true
151         notificationListContainer.setExpandingNotification(notification)
152 
153         jankMonitor.begin(notification,
154             InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
155     }
156 
onLaunchAnimationEndnull157     override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
158         jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
159 
160         notification.isExpandAnimationRunning = false
161         notificationShadeWindowViewController.setExpandAnimationRunning(false)
162         notificationEntry.isExpandAnimationRunning = false
163         notificationListContainer.setExpandingNotification(null)
164         applyParams(null)
165         removeHun(animate = false)
166         onFinishAnimationCallback?.run()
167     }
168 
applyParamsnull169     private fun applyParams(params: LaunchAnimationParameters?) {
170         notification.applyLaunchAnimationParams(params)
171         notificationListContainer.applyLaunchAnimationParams(params)
172     }
173 
onLaunchAnimationProgressnull174     override fun onLaunchAnimationProgress(
175         state: LaunchAnimator.State,
176         progress: Float,
177         linearProgress: Float
178     ) {
179         val params = state as LaunchAnimationParameters
180         params.progress = progress
181         params.linearProgress = linearProgress
182 
183         applyParams(params)
184     }
185 }
186