1 package com.android.systemui.screenshot 2 3 import android.animation.Animator 4 import android.animation.AnimatorListenerAdapter 5 import android.animation.ValueAnimator 6 import android.os.UserHandle 7 import android.view.View 8 import android.view.ViewGroup 9 import android.view.ViewGroup.MarginLayoutParams 10 import android.view.ViewTreeObserver 11 import android.view.animation.AccelerateDecelerateInterpolator 12 import androidx.constraintlayout.widget.Guideline 13 import com.android.systemui.R 14 import com.android.systemui.flags.FeatureFlags 15 import com.android.systemui.flags.Flags 16 import javax.inject.Inject 17 18 /** 19 * MessageContainerController controls the display of content in the screenshot message container. 20 */ 21 class MessageContainerController 22 @Inject 23 constructor( 24 private val workProfileMessageController: WorkProfileMessageController, 25 private val screenshotDetectionController: ScreenshotDetectionController, 26 private val featureFlags: FeatureFlags, 27 ) { 28 private lateinit var container: ViewGroup 29 private lateinit var guideline: Guideline 30 private lateinit var workProfileFirstRunView: ViewGroup 31 private lateinit var detectionNoticeView: ViewGroup 32 private var animateOut: Animator? = null 33 34 fun setView(screenshotView: ViewGroup) { 35 container = screenshotView.requireViewById(R.id.screenshot_message_container) 36 guideline = screenshotView.requireViewById(R.id.guideline) 37 38 workProfileFirstRunView = container.requireViewById(R.id.work_profile_first_run) 39 detectionNoticeView = container.requireViewById(R.id.screenshot_detection_notice) 40 41 // Restore to starting state. 42 container.visibility = View.GONE 43 guideline.setGuidelineEnd(0) 44 workProfileFirstRunView.visibility = View.GONE 45 detectionNoticeView.visibility = View.GONE 46 } 47 48 // Minimal implementation for use when Flags.SCREENSHOT_METADATA isn't turned on. 49 fun onScreenshotTaken(userHandle: UserHandle) { 50 val workProfileData = workProfileMessageController.onScreenshotTaken(userHandle) 51 if (workProfileData != null) { 52 workProfileFirstRunView.visibility = View.VISIBLE 53 detectionNoticeView.visibility = View.GONE 54 55 workProfileMessageController.populateView( 56 workProfileFirstRunView, 57 workProfileData, 58 this::animateOutMessageContainer 59 ) 60 animateInMessageContainer() 61 } 62 } 63 64 fun onScreenshotTaken(screenshot: ScreenshotData) { 65 val workProfileData = workProfileMessageController.onScreenshotTaken(screenshot.userHandle) 66 var notifiedApps: List<CharSequence> = listOf() 67 if (featureFlags.isEnabled(Flags.SCREENSHOT_DETECTION)) { 68 notifiedApps = screenshotDetectionController.maybeNotifyOfScreenshot(screenshot) 69 } 70 71 // If work profile first run needs to show, bias towards that, otherwise show screenshot 72 // detection notification if needed. 73 if (workProfileData != null) { 74 workProfileFirstRunView.visibility = View.VISIBLE 75 detectionNoticeView.visibility = View.GONE 76 workProfileMessageController.populateView( 77 workProfileFirstRunView, 78 workProfileData, 79 this::animateOutMessageContainer 80 ) 81 animateInMessageContainer() 82 } else if (notifiedApps.isNotEmpty()) { 83 detectionNoticeView.visibility = View.VISIBLE 84 workProfileFirstRunView.visibility = View.GONE 85 screenshotDetectionController.populateView(detectionNoticeView, notifiedApps) 86 animateInMessageContainer() 87 } 88 } 89 90 private fun animateInMessageContainer() { 91 if (container.visibility == View.VISIBLE) return 92 93 // Need the container to be fully measured before animating in (to know animation offset 94 // destination) 95 container.visibility = View.VISIBLE 96 container.viewTreeObserver.addOnPreDrawListener( 97 object : ViewTreeObserver.OnPreDrawListener { 98 override fun onPreDraw(): Boolean { 99 container.viewTreeObserver.removeOnPreDrawListener(this) 100 getAnimator(true).start() 101 return false 102 } 103 } 104 ) 105 } 106 107 private fun animateOutMessageContainer() { 108 if (animateOut != null) return 109 110 animateOut = 111 getAnimator(false).apply { 112 addListener( 113 object : AnimatorListenerAdapter() { 114 override fun onAnimationEnd(animation: Animator) { 115 super.onAnimationEnd(animation) 116 container.visibility = View.GONE 117 animateOut = null 118 } 119 } 120 ) 121 start() 122 } 123 } 124 125 private fun getAnimator(animateIn: Boolean): Animator { 126 val params = container.layoutParams as MarginLayoutParams 127 val offset = container.height + params.topMargin + params.bottomMargin 128 val anim = if (animateIn) ValueAnimator.ofFloat(0f, 1f) else ValueAnimator.ofFloat(1f, 0f) 129 with(anim) { 130 duration = ScreenshotView.SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS 131 interpolator = AccelerateDecelerateInterpolator() 132 addUpdateListener { valueAnimator: ValueAnimator -> 133 val interpolation = valueAnimator.animatedValue as Float 134 guideline.setGuidelineEnd((interpolation * offset).toInt()) 135 container.alpha = interpolation 136 } 137 } 138 return anim 139 } 140 } 141