<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