• 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 package com.android.wm.shell.windowdecor
17 
18 import android.animation.Animator
19 import android.animation.AnimatorListenerAdapter
20 import android.animation.ValueAnimator
21 import android.app.ActivityManager.RunningTaskInfo
22 import android.content.Context
23 import android.graphics.Color
24 import android.graphics.PixelFormat
25 import android.graphics.PointF
26 import android.graphics.Rect
27 import android.os.Trace
28 import android.view.Choreographer
29 import android.view.Display
30 import android.view.LayoutInflater
31 import android.view.SurfaceControl
32 import android.view.SurfaceControlViewHost
33 import android.view.WindowManager
34 import android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL
35 import android.view.WindowlessWindowManager
36 import android.widget.ImageView
37 import android.window.TaskConstants
38 import androidx.compose.material3.dynamicDarkColorScheme
39 import androidx.compose.material3.dynamicLightColorScheme
40 import androidx.compose.ui.graphics.toArgb
41 import com.android.internal.annotations.VisibleForTesting
42 import com.android.wm.shell.R
43 import com.android.wm.shell.common.DisplayController
44 import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
45 import com.android.wm.shell.shared.annotations.ShellBackgroundThread
46 import com.android.wm.shell.shared.annotations.ShellMainThread
47 import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
48 import com.android.wm.shell.windowdecor.common.WindowDecorTaskResourceLoader
49 import com.android.wm.shell.windowdecor.common.DecorThemeUtil
50 import com.android.wm.shell.windowdecor.common.Theme
51 import java.util.function.Supplier
52 import kotlinx.coroutines.CoroutineDispatcher
53 import kotlinx.coroutines.CoroutineScope
54 import kotlinx.coroutines.Job
55 import kotlinx.coroutines.isActive
56 import kotlinx.coroutines.launch
57 import kotlinx.coroutines.withContext
58 
59 /**
60  * Creates and updates a veil that covers task contents on resize.
61  */
62 public class ResizeVeil @JvmOverloads constructor(
63         private val context: Context,
64         private val displayController: DisplayController,
65         private val taskResourceLoader: WindowDecorTaskResourceLoader,
66         @ShellMainThread private val mainDispatcher: CoroutineDispatcher,
67         @ShellBackgroundThread private val bgScope: CoroutineScope,
68         private var parentSurface: SurfaceControl,
69         private val surfaceControlTransactionSupplier: Supplier<SurfaceControl.Transaction>,
70         private val surfaceControlBuilderFactory: SurfaceControlBuilderFactory =
71                 object : SurfaceControlBuilderFactory {},
72         private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory =
73                 object : SurfaceControlViewHostFactory {},
74         taskInfo: RunningTaskInfo,
75 ) {
76     private val decorThemeUtil = DecorThemeUtil(context)
77     private val lightColors = dynamicLightColorScheme(context)
78     private val darkColors = dynamicDarkColorScheme(context)
79 
80     @VisibleForTesting
81     lateinit var iconView: ImageView
82     private var iconSize = 0
83 
84     /** A container surface to host the veil background and icon child surfaces.  */
85     private var veilSurface: SurfaceControl? = null
86     /** A color surface for the veil background.  */
87     private var backgroundSurface: SurfaceControl? = null
88     /** A surface that hosts a windowless window with the app icon.  */
89     private var iconSurface: SurfaceControl? = null
90     private var viewHost: SurfaceControlViewHost? = null
91     private var display: Display? = null
92     private var veilAnimator: ValueAnimator? = null
93     private var iconAnimator: ValueAnimator? = null
94     private var loadAppInfoJob: Job? = null
95 
96     /**
97      * Whether the resize veil is currently visible.
98      *
99      * Note: when animating a [ResizeVeil.hideVeil], the veil is considered visible as soon
100      * as the animation starts.
101      */
102     private var isVisible = false
103 
104     private val onDisplaysChangedListener: OnDisplaysChangedListener =
105             object : OnDisplaysChangedListener {
onDisplayAddednull106                 override fun onDisplayAdded(displayId: Int) {
107                     if (taskInfo.displayId != displayId) {
108                         return
109                     }
110                     displayController.removeDisplayWindowListener(this)
111                     setupResizeVeil(taskInfo)
112                 }
113             }
114 
115     /**
116      * Whether the resize veil is ready to be shown.
117      */
118     private val isReady: Boolean
119         get() = viewHost != null
120 
121     init {
122         setupResizeVeil(taskInfo)
123     }
124 
125     /**
126      * Create the veil in its default invisible state.
127      */
setupResizeVeilnull128     private fun setupResizeVeil(taskInfo: RunningTaskInfo) {
129         if (!obtainDisplayOrRegisterListener(taskInfo.displayId)) {
130             // Display may not be available yet, skip this until then.
131             return
132         }
133         Trace.beginSection("ResizeVeil#setupResizeVeil")
134         veilSurface = surfaceControlBuilderFactory
135                 .create("Resize veil of Task=" + taskInfo.taskId)
136                 .setContainerLayer()
137                 .setHidden(true)
138                 .setParent(parentSurface)
139                 .setCallsite("ResizeVeil#setupResizeVeil")
140                 .build()
141         backgroundSurface = surfaceControlBuilderFactory
142                 .create("Resize veil background of Task=" + taskInfo.taskId)
143                 .setColorLayer()
144                 .setHidden(true)
145                 .setParent(veilSurface)
146                 .setCallsite("ResizeVeil#setupResizeVeil")
147                 .build()
148         iconSurface = surfaceControlBuilderFactory
149                 .create("Resize veil icon of Task=" + taskInfo.taskId)
150                 .setContainerLayer()
151                 .setHidden(true)
152                 .setParent(veilSurface)
153                 .setCallsite("ResizeVeil#setupResizeVeil")
154                 .build()
155         iconSize = context.resources
156                 .getDimensionPixelSize(R.dimen.desktop_mode_resize_veil_icon_size)
157         val root = LayoutInflater.from(context)
158                 .inflate(R.layout.desktop_mode_resize_veil, null /* root */)
159         iconView = root.requireViewById(R.id.veil_application_icon)
160         val lp = WindowManager.LayoutParams(
161                 iconSize,
162                 iconSize,
163                 WindowManager.LayoutParams.TYPE_APPLICATION,
164                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
165                 PixelFormat.TRANSPARENT)
166         lp.title = "Resize veil icon window of Task=" + taskInfo.taskId
167         lp.inputFeatures = INPUT_FEATURE_NO_INPUT_CHANNEL
168         lp.setTrustedOverlay()
169         val wwm = WindowlessWindowManager(taskInfo.configuration,
170                 iconSurface, null /* hostInputToken */)
171         viewHost = surfaceControlViewHostFactory.create(context, display, wwm, "ResizeVeil")
172         viewHost?.setView(root, lp)
173         loadAppInfoJob = bgScope.launch {
174             if (!isActive) return@launch
175             val icon = taskResourceLoader.getVeilIcon(taskInfo)
176             withContext(mainDispatcher) {
177                 if (!isActive) return@withContext
178                 iconView.setImageBitmap(icon)
179             }
180         }
181         Trace.endSection()
182     }
183 
obtainDisplayOrRegisterListenernull184     private fun obtainDisplayOrRegisterListener(displayId: Int): Boolean {
185         display = displayController.getDisplay(displayId)
186         if (display == null) {
187             displayController.addDisplayWindowListener(onDisplaysChangedListener)
188             return false
189         }
190         return true
191     }
192 
193     /**
194      * Shows the veil surface/view.
195      *
196      * @param t the transaction to apply in sync with the veil draw
197      * @param parent the surface that the veil should be a child of
198      * @param taskBounds the bounds of the task that owns the veil
199      * @param fadeIn if true, the veil will fade-in with an animation, if false, it will be shown
200      * immediately
201      */
showVeilnull202     fun showVeil(
203             t: SurfaceControl.Transaction,
204             parent: SurfaceControl,
205             taskBounds: Rect,
206             taskInfo: RunningTaskInfo,
207             fadeIn: Boolean,
208     ) {
209         if (!isReady || isVisible) {
210             t.apply()
211             return
212         }
213         val background = backgroundSurface
214         val icon = iconSurface
215         if (background == null || icon == null) return
216         updateTransactionWithShowVeil(
217             t,
218             parent,
219             taskBounds,
220             taskInfo,
221             fadeIn,
222         )
223         if (fadeIn) {
224             cancelAnimation()
225             val veilAnimT = surfaceControlTransactionSupplier.get()
226             val iconAnimT = surfaceControlTransactionSupplier.get()
227             veilAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
228                 duration = VEIL_ENTRY_ALPHA_ANIMATION_DURATION
229                 addUpdateListener {
230                     veilAnimT.setAlpha(background, animatedValue as Float)
231                             .apply()
232                 }
233                 addListener(object : AnimatorListenerAdapter() {
234                     override fun onAnimationStart(animation: Animator) {
235                         veilAnimT.show(background)
236                                 .setAlpha(background, 0f)
237                                 .apply()
238                     }
239 
240                     override fun onAnimationEnd(animation: Animator) {
241                         veilAnimT.setAlpha(background, 1f).apply()
242                     }
243                 })
244             }
245             iconAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
246                 duration = ICON_ALPHA_ANIMATION_DURATION
247                 startDelay = ICON_ENTRY_DELAY
248                 addUpdateListener {
249                     iconAnimT.setAlpha(icon, animatedValue as Float)
250                             .apply()
251                 }
252                 addListener(object : AnimatorListenerAdapter() {
253                     override fun onAnimationStart(animation: Animator) {
254                         iconAnimT.show(icon)
255                                 .setAlpha(icon, 0f)
256                                 .apply()
257                     }
258 
259                     override fun onAnimationEnd(animation: Animator) {
260                         iconAnimT.setAlpha(icon, 1f).apply()
261                     }
262                 })
263             }
264 
265             // Let the animators show it with the correct alpha value once the animation starts.
266             t.hide(icon)
267                     .hide(background)
268                     .apply()
269             veilAnimator?.start()
270             iconAnimator?.start()
271         } else {
272             // Show the veil immediately.
273             t.apply()
274         }
275     }
276 
updateTransactionWithShowVeilnull277     fun updateTransactionWithShowVeil(
278         t: SurfaceControl.Transaction,
279         parent: SurfaceControl,
280         taskBounds: Rect,
281         taskInfo: RunningTaskInfo,
282         fadeIn: Boolean = false,
283     ) {
284         if (!isReady || isVisible) return
285         isVisible = true
286         val background = backgroundSurface
287         val icon = iconSurface
288         val veil = veilSurface
289         if (background == null || icon == null || veil == null) return
290         // Parent surface can change, ensure it is up to date.
291         if (parent != parentSurface) {
292             t.reparent(veil, parent)
293             parentSurface = parent
294         }
295         val backgroundColor = when (decorThemeUtil.getAppTheme(taskInfo)) {
296             Theme.LIGHT -> lightColors.surfaceContainer
297             Theme.DARK -> darkColors.surfaceContainer
298         }
299         t.show(veil)
300             .setLayer(veil, VEIL_CONTAINER_LAYER)
301             .setLayer(icon, VEIL_ICON_LAYER)
302             .setLayer(background, VEIL_BACKGROUND_LAYER)
303             .setColor(background, Color.valueOf(backgroundColor.toArgb()).components)
304         relayout(taskBounds, t)
305         if (!fadeIn) {
306             t.show(icon)
307                 .show(background)
308                 .setAlpha(icon, 1f)
309                 .setAlpha(background, 1f)
310         }
311     }
312 
313     /**
314      * Animate veil's alpha to 1, fading it in.
315      */
showVeilnull316     fun showVeil(parentSurface: SurfaceControl, taskBounds: Rect, taskInfo: RunningTaskInfo) {
317         if (!isReady || isVisible) {
318             return
319         }
320         val t = surfaceControlTransactionSupplier.get()
321         showVeil(t, parentSurface, taskBounds, taskInfo, true /* fadeIn */)
322     }
323 
324     /**
325      * Update veil bounds to match bounds changes.
326      * @param newBounds bounds to update veil to.
327      */
relayoutnull328     private fun relayout(newBounds: Rect, t: SurfaceControl.Transaction) {
329         val iconPosition = calculateAppIconPosition(newBounds)
330         val veil = veilSurface
331         val icon = iconSurface
332         if (veil == null || icon == null) return
333         t.setWindowCrop(veil, newBounds.width(), newBounds.height())
334                 .setPosition(icon, iconPosition.x, iconPosition.y)
335                 .setPosition(parentSurface, newBounds.left.toFloat(), newBounds.top.toFloat())
336                 .setWindowCrop(parentSurface, newBounds.width(), newBounds.height())
337                 .setFrameTimeline(Choreographer.getInstance().vsyncId)
338     }
339 
340     /**
341      * Calls relayout to update task and veil bounds.
342      * @param newBounds bounds to update veil to.
343      */
updateResizeVeilnull344     fun updateResizeVeil(newBounds: Rect) {
345         if (!isVisible) {
346             return
347         }
348         val t = surfaceControlTransactionSupplier.get()
349         updateResizeVeil(t, newBounds)
350     }
351 
352     /**
353      * Calls relayout to update task and veil bounds.
354      * Finishes veil fade in if animation is currently running; this is to prevent empty space
355      * being visible behind the transparent veil during a fast resize.
356      *
357      * @param t a transaction to be applied in sync with the veil draw.
358      * @param newBounds bounds to update veil to.
359      */
updateResizeVeilnull360     fun updateResizeVeil(t: SurfaceControl.Transaction, newBounds: Rect) {
361         updateTransactionWithResizeVeil(t, newBounds)
362         t.apply()
363     }
364 
updateTransactionWithResizeVeilnull365     fun updateTransactionWithResizeVeil(t: SurfaceControl.Transaction, newBounds: Rect) {
366         if (!isVisible) {
367             return
368         }
369         veilAnimator?.let { animator ->
370             if (animator.isStarted) {
371                 animator.removeAllUpdateListeners()
372                 animator.end()
373             }
374         }
375         relayout(newBounds, t)
376     }
377 
378     /**
379      * Animate veil's alpha to 0, fading it out.
380      */
hideVeilnull381     fun hideVeil() {
382         if (!isVisible) {
383             return
384         }
385         cancelAnimation()
386         val background = backgroundSurface
387         val icon = iconSurface
388         if (background == null || icon == null) return
389 
390         veilAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
391             duration = VEIL_EXIT_ALPHA_ANIMATION_DURATION
392             startDelay = VEIL_EXIT_DELAY
393             addUpdateListener {
394                 surfaceControlTransactionSupplier.get()
395                         .setAlpha(background, animatedValue as Float)
396                         .apply()
397             }
398             addListener(object : AnimatorListenerAdapter() {
399                 override fun onAnimationEnd(animation: Animator) {
400                     surfaceControlTransactionSupplier.get()
401                             .hide(background)
402                             .apply()
403                 }
404             })
405         }
406         iconAnimator = ValueAnimator.ofFloat(1f, 0f).apply {
407             duration = ICON_ALPHA_ANIMATION_DURATION
408             addUpdateListener {
409                 surfaceControlTransactionSupplier.get()
410                     .setAlpha(icon, animatedValue as Float)
411                     .apply()
412             }
413             addListener(object : AnimatorListenerAdapter() {
414                 override fun onAnimationEnd(animation: Animator) {
415                     surfaceControlTransactionSupplier.get()
416                         .hide(icon)
417                         .apply()
418                 }
419             })
420         }
421         veilAnimator?.start()
422         iconAnimator?.start()
423         isVisible = false
424     }
425 
calculateAppIconPositionnull426     private fun calculateAppIconPosition(parentBounds: Rect): PointF {
427         return PointF(parentBounds.width().toFloat() / 2 - iconSize.toFloat() / 2,
428                 parentBounds.height().toFloat() / 2 - iconSize.toFloat() / 2)
429     }
430 
cancelAnimationnull431     private fun cancelAnimation() {
432         veilAnimator?.removeAllUpdateListeners()
433         veilAnimator?.cancel()
434         veilAnimator = null
435         iconAnimator?.removeAllUpdateListeners()
436         iconAnimator?.cancel()
437         iconAnimator = null
438     }
439 
440     /**
441      * Dispose of veil when it is no longer needed, likely on close of its container decor.
442      */
disposenull443     fun dispose() {
444         cancelAnimation()
445         isVisible = false
446         loadAppInfoJob?.cancel()
447 
448         viewHost?.release()
449         viewHost = null
450 
451         val t: SurfaceControl.Transaction = surfaceControlTransactionSupplier.get()
452         backgroundSurface?.let { background -> t.remove(background) }
453         backgroundSurface = null
454         iconSurface?.let { icon -> t.remove(icon) }
455         iconSurface = null
456         veilSurface?.let { veil -> t.remove(veil) }
457         veilSurface = null
458         t.apply()
459         displayController.removeDisplayWindowListener(onDisplaysChangedListener)
460     }
461 
462     interface SurfaceControlBuilderFactory {
createnull463         fun create(name: String): SurfaceControl.Builder {
464             return SurfaceControl.Builder().setName(name)
465         }
466     }
467 
468     companion object {
469         private const val TAG = "ResizeVeil"
470         private const val ICON_ALPHA_ANIMATION_DURATION = 50L
471         private const val VEIL_ENTRY_ALPHA_ANIMATION_DURATION = 50L
472         private const val VEIL_EXIT_ALPHA_ANIMATION_DURATION = 200L
473         private const val ICON_ENTRY_DELAY = 33L
474         private const val VEIL_EXIT_DELAY = 33L
475         private const val VEIL_CONTAINER_LAYER = TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL
476 
477         /** The background is a child of the veil container layer and goes at the bottom.  */
478         private const val VEIL_BACKGROUND_LAYER = 0
479 
480         /** The icon is a child of the veil container layer and goes in front of the background.  */
481         private const val VEIL_ICON_LAYER = 1
482     }
483 }
484