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