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