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