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