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