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