xref: /aosp_15_r20/platform_testing/libraries/flicker/src/android/tools/flicker/config/TransitionFilters.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
1 /*
<lambda>null2  * Copyright (C) 2023 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 android.tools.flicker.config
18 
19 import android.tools.CrossPlatform
20 import android.tools.PlatformConsts.SPLIT_SCREEN_TRANSITION_HANDLER
21 import android.tools.flicker.extractors.ITransitionMatcher
22 import android.tools.flicker.extractors.TransitionsTransform
23 import android.tools.flicker.isAppTransitionChange
24 import android.tools.traces.component.ComponentNameMatcher
25 import android.tools.traces.surfaceflinger.LayersTrace
26 import android.tools.traces.wm.Transition
27 import android.tools.traces.wm.TransitionType
28 import android.tools.traces.wm.TransitionType.TO_BACK
29 import android.tools.traces.wm.TransitionType.TO_FRONT
30 import android.tools.traces.wm.WmTransitionData
31 
32 object TransitionFilters {
33     val OPEN_APP_TRANSITION_FILTER: TransitionsTransform = { ts, _, _ ->
34         ts.filter { t ->
35             t.changes.any {
36                 it.transitMode == TransitionType.OPEN || // cold launch
37                     it.transitMode == TO_FRONT // warm launch
38             }
39         }
40     }
41 
42     val CLOSE_APP_TO_LAUNCHER_FILTER: TransitionsTransform = { ts, _, reader ->
43         val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
44         val layers = layersTrace.entries.flatMap { it.flattenedLayers }.distinctBy { it.id }
45         val launcherLayers = layers.filter { ComponentNameMatcher.LAUNCHER.layerMatchesAnyOf(it) }
46 
47         ts.filter { t ->
48             t.changes.any { it.transitMode == TransitionType.CLOSE || it.transitMode == TO_BACK } &&
49                 t.changes.any { change ->
50                     launcherLayers.any { it.id == change.layerId }
51                     change.transitMode == TO_FRONT
52                 }
53         }
54     }
55 
56     val QUICK_SWITCH_TRANSITION_FILTER: TransitionsTransform = { ts, _, reader ->
57         val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
58         val wmTrace = reader.readWmTrace()
59 
60         val mergedTransitions = ts.filter { it.mergeTarget != null }
61         val nonMergedTransitions =
62             mutableMapOf<Int, Transition>().apply {
63                 ts.filter { it.mergeTarget == null }.forEach { this@apply[it.id] = it }
64             }
65         mergedTransitions.forEach {
66             val mergedInto = it.mergeTarget ?: error("Missing merged into id!")
67             val mergedTransition = nonMergedTransitions[mergedInto]?.merge(it)
68             if (mergedTransition != null) {
69                 nonMergedTransitions[mergedInto] = mergedTransition
70             }
71         }
72 
73         val artificiallyMergedTransitions = nonMergedTransitions.values
74 
75         val quickswitchBetweenAppsTransitions =
76             artificiallyMergedTransitions.filter { transition ->
77                 val openingAppLayers =
78                     transition.changes.filter {
79                         it.transitMode == TO_FRONT &&
80                             isAppTransitionChange(it, layersTrace, wmTrace) &&
81                             !isWallpaperTokenLayer(it.layerId, layersTrace) &&
82                             !isLauncherTopLevelTaskLayer(it.layerId, layersTrace)
83                     }
84                 val closingAppLayers =
85                     transition.changes.filter {
86                         it.transitMode == TO_BACK && isAppTransitionChange(it, layersTrace, wmTrace)
87                     }
88 
89                 transition.handler == TransitionHandler.RECENTS &&
90                     transition.changes.count {
91                         it.transitMode == TO_FRONT && isWallpaperTokenLayer(it.layerId, layersTrace)
92                     } == 1 &&
93                     transition.changes.count {
94                         it.transitMode == TO_FRONT &&
95                             isLauncherTopLevelTaskLayer(it.layerId, layersTrace)
96                     } == 1 &&
97                     (openingAppLayers.count() == 1 || openingAppLayers.count() == 5) &&
98                     // Fullscreen: 1 app + 1 recents
99                     // SplitScreen: 2 apps + 2 split containers + 1 split root + 1 recents
100                     (closingAppLayers.count() == 2 || closingAppLayers.count() == 6)
101             }
102 
103         var quickswitchFromLauncherTransitions =
104             artificiallyMergedTransitions.filter { transition ->
105                 val openingAppLayers =
106                     transition.changes.filter {
107                         it.transitMode == TO_FRONT &&
108                             isAppTransitionChange(it, layersTrace, wmTrace)
109                     }
110 
111                 transition.handler == TransitionHandler.DEFAULT &&
112                     (openingAppLayers.count() == 1 || openingAppLayers.count() == 5) &&
113                     transition.changes.count {
114                         it.transitMode == TO_BACK &&
115                             isLauncherTopLevelTaskLayer(it.layerId, layersTrace)
116                     } == 1
117             }
118 
119         // TODO: (b/300068479) temporary work around to ensure transition is associated with CUJ
120         val hundredMs = CrossPlatform.timestamp.from(elapsedNanos = 100000000L)
121 
122         quickswitchFromLauncherTransitions =
123             quickswitchFromLauncherTransitions.map {
124                 // We create the transition right about the same time we end the CUJ tag
125                 val createTimeAdjustedForTolerance = it.wmData.createTime?.minus(hundredMs)
126                 Transition(
127                     id = it.id,
128                     wmData =
129                         it.wmData.merge(
130                             WmTransitionData(
131                                 createTime = createTimeAdjustedForTolerance,
132                                 sendTime = createTimeAdjustedForTolerance,
133                             )
134                         ),
135                     shellData = it.shellData,
136                 )
137             }
138 
139         quickswitchBetweenAppsTransitions + quickswitchFromLauncherTransitions
140     }
141 
142     val QUICK_SWITCH_TRANSITION_POST_PROCESSING: TransitionsTransform = { transitions, _, reader ->
143         require(transitions.size == 1) { "Expected 1 transition but got ${transitions.size}" }
144 
145         val transition = transitions.first()
146 
147         val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
148         val wallpaperId =
149             transition.changes
150                 .map { it.layerId }
151                 .firstOrNull { isWallpaperTokenLayer(it, layersTrace) }
152         val isSwitchFromLauncher = wallpaperId == null
153         val launcherId =
154             if (isSwitchFromLauncher) null
155             else
156                 transition.changes
157                     .map { it.layerId }
158                     .firstOrNull { isLauncherTopLevelTaskLayer(it, layersTrace) }
159                     ?: error("Missing launcher layer in transition")
160 
161         val filteredChanges =
162             transition.changes.filter { it.layerId != wallpaperId && it.layerId != launcherId }
163 
164         val closingAppChange = filteredChanges.first { it.transitMode == TO_BACK }
165         val openingAppChange = filteredChanges.first { it.transitMode == TO_FRONT }
166 
167         // Transition removing the intermediate launcher changes
168         listOf(
169             Transition(
170                 transition.id,
171                 WmTransitionData(
172                     createTime = transition.wmData.createTime,
173                     sendTime = transition.wmData.sendTime,
174                     abortTime = transition.wmData.abortTime,
175                     finishTime = transition.wmData.finishTime,
176                     startingWindowRemoveTime = transition.wmData.startingWindowRemoveTime,
177                     startTransactionId = transition.wmData.startTransactionId,
178                     finishTransactionId = transition.wmData.finishTransactionId,
179                     type = transition.wmData.type,
180                     changes = listOf(closingAppChange, openingAppChange),
181                 ),
182                 transition.shellData,
183             )
184         )
185     }
186 
187     private fun isLauncherTopLevelTaskLayer(layerId: Int, layersTrace: LayersTrace): Boolean {
188         return layersTrace.entries.any { entry ->
189             val launcherLayer =
190                 entry.flattenedLayers.firstOrNull { layer ->
191                     ComponentNameMatcher.LAUNCHER.or(ComponentNameMatcher.AOSP_LAUNCHER)
192                         .layerMatchesAnyOf(layer)
193                 } ?: return@any false
194 
195             var curLayer = launcherLayer
196             while (!curLayer.isTask && curLayer.parent != null) {
197                 curLayer = curLayer.parent ?: error("unreachable")
198             }
199             if (!curLayer.isTask) {
200                 error("Expected a task layer above the launcher layer")
201             }
202 
203             var launcherTopLevelTaskLayer = curLayer
204             // Might have nested task layers
205             while (
206                 launcherTopLevelTaskLayer.parent != null &&
207                     launcherTopLevelTaskLayer.parent!!.isTask
208             ) {
209                 launcherTopLevelTaskLayer = launcherTopLevelTaskLayer.parent ?: error("unreachable")
210             }
211 
212             return@any launcherTopLevelTaskLayer.id == layerId
213         }
214     }
215 
216     private fun isWallpaperTokenLayer(layerId: Int, layersTrace: LayersTrace): Boolean {
217         return layersTrace.entries.any { entry ->
218             entry.flattenedLayers.any { layer ->
219                 layer.id == layerId &&
220                     ComponentNameMatcher.WALLPAPER_WINDOW_TOKEN.layerMatchesAnyOf(layer)
221             }
222         }
223     }
224 
225     val APP_CLOSE_TO_PIP_TRANSITION_FILTER: TransitionsTransform = { ts, _, _ ->
226         ts.filter { it.type == TransitionType.PIP }
227     }
228 
229     val ENTER_SPLIT_SCREEN_MATCHER =
230         object : ITransitionMatcher {
231             override fun findAll(transitions: Collection<Transition>): Collection<Transition> {
232                 return transitions.filter { isSplitscreenEnterTransition(it) }
233             }
234         }
235 
236     val EXIT_SPLIT_SCREEN_FILTER: TransitionsTransform = { ts, _, _ ->
237         ts.filter { isSplitscreenExitTransition(it) }
238     }
239 
240     val RESIZE_SPLIT_SCREEN_FILTER: TransitionsTransform = { ts, _, _ ->
241         ts.filter { isSplitscreenResizeTransition(it) }
242     }
243 
244     fun isSplitscreenEnterTransition(transition: Transition): Boolean {
245         return transition.handler == SPLIT_SCREEN_TRANSITION_HANDLER && transition.type == TO_FRONT
246     }
247 
248     fun isSplitscreenExitTransition(transition: Transition): Boolean {
249         return transition.type == TransitionType.SPLIT_DISMISS ||
250             transition.type == TransitionType.SPLIT_DISMISS_SNAP
251     }
252 
253     fun isSplitscreenResizeTransition(transition: Transition): Boolean {
254         // This transition doesn't have a special type
255         return transition.type == TransitionType.CHANGE &&
256             transition.changes.size == 2 &&
257             transition.changes.all { change -> change.transitMode == TransitionType.CHANGE }
258     }
259 }
260