• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wm.shell.windowdecor.education
18 
19 import android.annotation.ColorInt
20 import android.annotation.DimenRes
21 import android.annotation.LayoutRes
22 import android.content.Context
23 import android.content.res.Resources
24 import android.graphics.Point
25 import android.view.LayoutInflater
26 import android.view.MotionEvent
27 import android.view.View
28 import android.view.WindowManager
29 import android.widget.LinearLayout
30 import android.widget.TextView
31 import android.window.DisplayAreaInfo
32 import android.window.WindowContainerTransaction
33 import androidx.dynamicanimation.animation.DynamicAnimation
34 import androidx.dynamicanimation.animation.SpringForce
35 import com.android.wm.shell.R
36 import com.android.wm.shell.common.DisplayChangeController.OnDisplayChangingListener
37 import com.android.wm.shell.common.DisplayController
38 import com.android.wm.shell.shared.animation.PhysicsAnimator
39 import com.android.wm.shell.windowdecor.WindowManagerWrapper
40 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
41 
42 /**
43  * Controls the lifecycle of an education promo, including showing and hiding it.
44  */
45 class DesktopWindowingEducationPromoController(
46     private val context: Context,
47     private val additionalSystemViewContainerFactory: AdditionalSystemViewContainer.Factory,
48     private val displayController: DisplayController,
49 ) : OnDisplayChangingListener {
50     private var educationView: View? = null
51     private var animator: PhysicsAnimator<View>? = null
52     private val springConfig by lazy {
53         PhysicsAnimator.SpringConfig(
54             SpringForce.STIFFNESS_MEDIUM,
55             SpringForce.DAMPING_RATIO_LOW_BOUNCY
56         )
57     }
58     private var popupWindow: AdditionalSystemViewContainer? = null
59 
60     override fun onDisplayChange(
61         displayId: Int,
62         fromRotation: Int,
63         toRotation: Int,
64         newDisplayAreaInfo: DisplayAreaInfo?,
65         t: WindowContainerTransaction?
66     ) {
67         // Exit if the rotation hasn't changed or is changed by 180 degrees. [fromRotation] and
68         // [toRotation] can be one of the [@Surface.Rotation] values.
69         if ((fromRotation % 2 == toRotation % 2)) return
70         hideEducation()
71     }
72 
73     /**
74      * Shows education promo.
75      *
76      * @param viewConfig features of the education.
77      * @param taskId is used in the title of popup window created for the education view.
78      */
79     fun showEducation(
80         viewConfig: EducationViewConfig,
81         taskId: Int
82     ) {
83         hideEducation()
84         educationView = createEducationView(viewConfig, taskId)
85         animator = createAnimator()
86         animateShowEducationTransition()
87         displayController.addDisplayChangingController(this)
88     }
89 
90     /** Hide the current education view if visible */
91     private fun hideEducation() = animateHideEducationTransition { cleanUp() }
92 
93     /** Create education view by inflating layout provided. */
94     private fun createEducationView(
95         viewConfig: EducationViewConfig,
96         taskId: Int
97     ): View {
98         val educationView =
99             LayoutInflater.from(context)
100                 .inflate(
101                     viewConfig.viewLayout, /* root= */ null, /* attachToRoot= */ false)
102                 .apply {
103                     alpha = 0f
104                     scaleX = 0f
105                     scaleY = 0f
106 
107                     requireViewById<TextView>(R.id.education_text).apply {
108                         text = viewConfig.educationText
109                     }
110                     setOnTouchListener { _, motionEvent ->
111                         if (motionEvent.action == MotionEvent.ACTION_OUTSIDE) {
112                             hideEducation()
113                             true
114                         } else {
115                             false
116                         }
117                     }
118                     setOnClickListener {
119                         hideEducation()
120                     }
121                     setEducationColorScheme(viewConfig.educationColorScheme)
122                 }
123 
124         createEducationPopupWindow(
125             taskId,
126             viewConfig.viewGlobalCoordinates,
127             loadDimensionPixelSize(viewConfig.widthId),
128             loadDimensionPixelSize(viewConfig.heightId),
129             educationView = educationView)
130 
131         return educationView
132     }
133 
134     /** Create animator for education transitions */
135     private fun createAnimator(): PhysicsAnimator<View>? =
136         educationView?.let {
137             PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) }
138         }
139 
140     /** Animate show transition for the education view */
141     private fun animateShowEducationTransition() {
142         animator
143             ?.spring(DynamicAnimation.ALPHA, 1f)
144             ?.spring(DynamicAnimation.SCALE_X, 1f)
145             ?.spring(DynamicAnimation.SCALE_Y, 1f)
146             ?.start()
147     }
148 
149     /** Animate hide transition for the education view */
150     private fun animateHideEducationTransition(endActions: () -> Unit) {
151         animator
152             ?.spring(DynamicAnimation.ALPHA, 0f)
153             ?.spring(DynamicAnimation.SCALE_X, 0f)
154             ?.spring(DynamicAnimation.SCALE_Y, 0f)
155             ?.start()
156         endActions()
157     }
158 
159     /** Remove education promo and clean up all relative properties */
160     private fun cleanUp() {
161         educationView = null
162         animator = null
163         popupWindow?.releaseView()
164         popupWindow = null
165         displayController.removeDisplayChangingController(this)
166     }
167 
168     private fun createEducationPopupWindow(
169         taskId: Int,
170         educationViewGlobalCoordinates: Point,
171         width: Int,
172         height: Int,
173         educationView: View,
174     ) {
175         popupWindow =
176             additionalSystemViewContainerFactory.create(
177                 windowManagerWrapper =
178                 WindowManagerWrapper(context.getSystemService(WindowManager::class.java)),
179                 taskId = taskId,
180                 x = educationViewGlobalCoordinates.x,
181                 y = educationViewGlobalCoordinates.y,
182                 width = width,
183                 height = height,
184                 flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
185                         WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
186                 view = educationView)
187     }
188 
189     private fun View.setEducationColorScheme(educationColorScheme: EducationColorScheme) {
190         requireViewById<LinearLayout>(R.id.education_container).apply {
191             background.setTint(educationColorScheme.container)
192         }
193         requireViewById<TextView>(R.id.education_text).apply {
194             setTextColor(educationColorScheme.text)
195         }
196     }
197 
198     private fun loadDimensionPixelSize(@DimenRes resourceId: Int): Int {
199         if (resourceId == Resources.ID_NULL) return 0
200         return context.resources.getDimensionPixelSize(resourceId)
201     }
202 
203     /**
204      * The configuration for education view features:
205      *
206      * @property viewLayout Layout resource ID of the view to be used for education promo.
207      * @property viewGlobalCoordinates Global (screen) coordinates of the education.
208      * @property educationText Text to be added to the TextView of the promo.
209      * @property widthId res Id for education width
210      * @property heightId res Id for education height
211      */
212     data class EducationViewConfig(
213         @LayoutRes val viewLayout: Int,
214         val educationColorScheme: EducationColorScheme,
215         val viewGlobalCoordinates: Point,
216         val educationText: String,
217         @DimenRes val widthId: Int,
218         @DimenRes val heightId: Int
219     )
220 
221     /**
222      * Color scheme of education view:
223      *
224      * @property container Color of the container of the education.
225      * @property text Text color of the [TextView] of education promo.
226      */
227     data class EducationColorScheme(
228         @ColorInt val container: Int,
229         @ColorInt val text: Int,
230     )
231 }
232