• 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.desktopmode.compatui
18 
19 import android.animation.ValueAnimator
20 import android.app.ActivityManager.RunningTaskInfo
21 import android.content.Context
22 import android.os.IBinder
23 import android.view.Display.DEFAULT_DISPLAY
24 import android.view.SurfaceControl
25 import android.window.TransitionInfo
26 import android.window.TransitionRequestInfo
27 import android.window.WindowContainerTransaction
28 import androidx.core.animation.addListener
29 import com.android.app.animation.Interpolators
30 import com.android.internal.protolog.ProtoLog
31 import com.android.wm.shell.common.ShellExecutor
32 import com.android.wm.shell.desktopmode.DesktopUserRepositories
33 import com.android.wm.shell.desktopmode.DesktopWallpaperActivity
34 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
35 import com.android.wm.shell.shared.TransitionUtil.isClosingMode
36 import com.android.wm.shell.shared.TransitionUtil.isClosingType
37 import com.android.wm.shell.shared.TransitionUtil.isOpeningMode
38 import com.android.wm.shell.shared.TransitionUtil.isOpeningType
39 import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
40 import com.android.wm.shell.sysui.ShellInit
41 import com.android.wm.shell.transition.Transitions
42 import com.android.wm.shell.transition.Transitions.TransitionHandler
43 
44 /** Handles transitions related to system modals, e.g. launch and close transitions. */
45 class SystemModalsTransitionHandler(
46     private val context: Context,
47     private val mainExecutor: ShellExecutor,
48     private val animExecutor: ShellExecutor,
49     private val shellInit: ShellInit,
50     private val transitions: Transitions,
51     private val desktopUserRepositories: DesktopUserRepositories,
52     private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
53 ) : TransitionHandler {
54 
55     private val showingSystemModalsIds = mutableSetOf<Int>()
56 
57     init {
58         shellInit.addInitCallback({ transitions.addHandler(this) }, this)
59     }
60 
61     override fun startAnimation(
62         transition: IBinder,
63         info: TransitionInfo,
64         startTransaction: SurfaceControl.Transaction,
65         finishTransaction: SurfaceControl.Transaction,
66         finishCallback: Transitions.TransitionFinishCallback,
67     ): Boolean {
68         if (!isDesktopModeShowing(DEFAULT_DISPLAY)) return false
69         if (isOpeningType(info.type)) {
70             val launchChange = getLaunchingSystemModal(info) ?: return false
71             val taskInfo = launchChange.taskInfo
72             requireNotNull(taskInfo)
73             logV("Animating system modal launch: taskId=%d", taskInfo.taskId)
74             showingSystemModalsIds.add(taskInfo.taskId)
75             animateSystemModal(
76                 launchChange.leash,
77                 startTransaction,
78                 finishTransaction,
79                 finishCallback,
80                 /* toShow= */ true,
81             )
82             return true
83         }
84         if (isClosingType(info.type)) {
85             val closeChange = getClosingSystemModal(info) ?: return false
86             val taskInfo = closeChange.taskInfo
87             requireNotNull(taskInfo)
88             logV("Animating system modal close: taskId=%d", taskInfo.taskId)
89             showingSystemModalsIds.remove(taskInfo.taskId)
90             animateSystemModal(
91                 closeChange.leash,
92                 startTransaction,
93                 finishTransaction,
94                 finishCallback,
95                 /* toShow= */ false,
96             )
97             return true
98         }
99         return false
100     }
101 
102     private fun animateSystemModal(
103         leash: SurfaceControl,
104         startTransaction: SurfaceControl.Transaction,
105         finishTransaction: SurfaceControl.Transaction,
106         finishCallback: Transitions.TransitionFinishCallback,
107         toShow: Boolean, // Whether to show or to hide the system modal
108     ) {
109         val startAlpha = if (toShow) 0f else 1f
110         val endAlpha = if (toShow) 1f else 0f
111         val animator =
112             createAlphaAnimator(SurfaceControl.Transaction(), leash, startAlpha, endAlpha)
113         animator.addListener(
114             onEnd = { _ ->
115                 mainExecutor.execute { finishCallback.onTransitionFinished(/* wct= */ null) }
116             }
117         )
118         if (toShow) {
119             finishTransaction.show(leash)
120         } else {
121             finishTransaction.hide(leash)
122         }
123         startTransaction.setAlpha(leash, startAlpha)
124         startTransaction.apply()
125         animExecutor.execute { animator.start() }
126     }
127 
128     private fun getLaunchingSystemModal(info: TransitionInfo): TransitionInfo.Change? =
129         info.changes.find { change ->
130             if (!isOpeningMode(change.mode)) {
131                 return@find false
132             }
133             val taskInfo = change.taskInfo ?: return@find false
134             return@find isSystemModal(taskInfo)
135         }
136 
137     private fun getClosingSystemModal(info: TransitionInfo): TransitionInfo.Change? =
138         info.changes.find { change ->
139             if (!isClosingMode(change.mode)) {
140                 return@find false
141             }
142             val taskInfo = change.taskInfo ?: return@find false
143             return@find isSystemModal(taskInfo) || showingSystemModalsIds.contains(taskInfo.taskId)
144         }
145 
146     private fun isSystemModal(taskInfo: RunningTaskInfo): Boolean =
147         !DesktopWallpaperActivity.isWallpaperTask(taskInfo) &&
148             desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(taskInfo)
149 
150     private fun createAlphaAnimator(
151         transaction: SurfaceControl.Transaction,
152         leash: SurfaceControl,
153         startVal: Float,
154         endVal: Float,
155     ): ValueAnimator =
156         ValueAnimator.ofFloat(startVal, endVal).apply {
157             duration = LAUNCH_ANIM_ALPHA_DURATION_MS
158             interpolator = Interpolators.LINEAR
159             addUpdateListener { animation ->
160                 transaction.setAlpha(leash, animation.animatedValue as Float).apply()
161             }
162         }
163 
164     private fun isDesktopModeShowing(displayId: Int): Boolean =
165         desktopUserRepositories.current.isAnyDeskActive(displayId)
166 
167     override fun handleRequest(
168         transition: IBinder,
169         request: TransitionRequestInfo,
170     ): WindowContainerTransaction? = null
171 
172     private fun logV(msg: String, vararg arguments: Any?) {
173         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
174     }
175 
176     companion object {
177         private const val TAG = "SystemModalsTransitionHandler"
178         private const val LAUNCH_ANIM_ALPHA_DURATION_MS = 150L
179     }
180 }
181