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                     (closingAppLayers.count() == 1 || closingAppLayers.count() == 5)
99             }
100 
101         var quickswitchFromLauncherTransitions =
102             artificiallyMergedTransitions.filter { transition ->
103                 val openingAppLayers =
104                     transition.changes.filter {
105                         it.transitMode == TO_FRONT &&
106                             isAppTransitionChange(it, layersTrace, wmTrace)
107                     }
108 
109                 transition.handler == TransitionHandler.DEFAULT &&
110                     (openingAppLayers.count() == 1 || openingAppLayers.count() == 5) &&
111                     transition.changes.count {
112                         it.transitMode == TO_BACK &&
113                             isLauncherTopLevelTaskLayer(it.layerId, layersTrace)
114                     } == 1
115             }
116 
117         // TODO: (b/300068479) temporary work around to ensure transition is associated with CUJ
118         val hundredMs = CrossPlatform.timestamp.from(elapsedNanos = 100000000L)
119 
120         quickswitchFromLauncherTransitions =
121             quickswitchFromLauncherTransitions.map {
122                 // We create the transition right about the same time we end the CUJ tag
123                 val createTimeAdjustedForTolerance = it.wmData.createTime?.minus(hundredMs)
124                 Transition(
125                     id = it.id,
126                     wmData =
127                         it.wmData.merge(
128                             WmTransitionData(
129                                 createTime = createTimeAdjustedForTolerance,
130                                 sendTime = createTimeAdjustedForTolerance
131                             )
132                         ),
133                     shellData = it.shellData
134                 )
135             }
136 
137         quickswitchBetweenAppsTransitions + quickswitchFromLauncherTransitions
138     }
139 
140     val QUICK_SWITCH_TRANSITION_POST_PROCESSING: TransitionsTransform = { transitions, _, reader ->
141         require(transitions.size == 1) { "Expected 1 transition but got ${transitions.size}" }
142 
143         val transition = transitions.first()
144 
145         val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
146         val wallpaperId =
147             transition.changes
148                 .map { it.layerId }
149                 .firstOrNull { isWallpaperTokenLayer(it, layersTrace) }
150         val isSwitchFromLauncher = wallpaperId == null
151         val launcherId =
152             if (isSwitchFromLauncher) null
153             else
154                 transition.changes
155                     .map { it.layerId }
156                     .firstOrNull { isLauncherTopLevelTaskLayer(it, layersTrace) }
157                     ?: error("Missing launcher layer in transition")
158 
159         val filteredChanges =
160             transition.changes.filter { it.layerId != wallpaperId && it.layerId != launcherId }
161 
162         val closingAppChange = filteredChanges.first { it.transitMode == TO_BACK }
163         val openingAppChange = filteredChanges.first { it.transitMode == TO_FRONT }
164 
165         // Transition removing the intermediate launcher changes
166         listOf(
167             Transition(
168                 transition.id,
169                 WmTransitionData(
170                     createTime = transition.wmData.createTime,
171                     sendTime = transition.wmData.sendTime,
172                     abortTime = transition.wmData.abortTime,
173                     finishTime = transition.wmData.finishTime,
174                     startingWindowRemoveTime = transition.wmData.startingWindowRemoveTime,
175                     startTransactionId = transition.wmData.startTransactionId,
176                     finishTransactionId = transition.wmData.finishTransactionId,
177                     type = transition.wmData.type,
178                     changes = listOf(closingAppChange, openingAppChange),
179                 ),
180                 transition.shellData
181             )
182         )
183     }
184 
185     private fun isLauncherTopLevelTaskLayer(layerId: Int, layersTrace: LayersTrace): Boolean {
186         return layersTrace.entries.any { entry ->
187             val launcherLayer =
188                 entry.flattenedLayers.firstOrNull { layer ->
189                     ComponentNameMatcher.LAUNCHER.or(ComponentNameMatcher.AOSP_LAUNCHER)
190                         .layerMatchesAnyOf(layer)
191                 }
192                     ?: return@any false
193 
194             var curLayer = launcherLayer
195             while (!curLayer.isTask && curLayer.parent != null) {
196                 curLayer = curLayer.parent ?: error("unreachable")
197             }
198             if (!curLayer.isTask) {
199                 error("Expected a task layer above the launcher layer")
200             }
201 
202             var launcherTopLevelTaskLayer = curLayer
203             // Might have nested task layers
204             while (
205                 launcherTopLevelTaskLayer.parent != null &&
206                     launcherTopLevelTaskLayer.parent!!.isTask
207             ) {
208                 launcherTopLevelTaskLayer = launcherTopLevelTaskLayer.parent ?: error("unreachable")
209             }
210 
211             return@any launcherTopLevelTaskLayer.id == layerId
212         }
213     }
214 
215     private fun isWallpaperTokenLayer(layerId: Int, layersTrace: LayersTrace): Boolean {
216         return layersTrace.entries.any { entry ->
217             entry.flattenedLayers.any { layer ->
218                 layer.id == layerId &&
219                     ComponentNameMatcher.WALLPAPER_WINDOW_TOKEN.layerMatchesAnyOf(layer)
220             }
221         }
222     }
223 
224     val APP_CLOSE_TO_PIP_TRANSITION_FILTER: TransitionsTransform = { ts, _, _ ->
225         ts.filter { it.type == TransitionType.PIP }
226     }
227 
228     val ENTER_SPLIT_SCREEN_MATCHER =
229         object : ITransitionMatcher {
230             override fun findAll(transitions: Collection<Transition>): Collection<Transition> {
231                 return transitions.filter { isSplitscreenEnterTransition(it) }
232             }
233         }
234 
235     val EXIT_SPLIT_SCREEN_FILTER: TransitionsTransform = { ts, _, _ ->
236         ts.filter { isSplitscreenExitTransition(it) }
237     }
238 
239     val RESIZE_SPLIT_SCREEN_FILTER: TransitionsTransform = { ts, _, _ ->
240         ts.filter { isSplitscreenResizeTransition(it) }
241     }
242 
243     fun isSplitscreenEnterTransition(transition: Transition): Boolean {
244         return transition.handler == SPLIT_SCREEN_TRANSITION_HANDLER && transition.type == TO_FRONT
245     }
246 
247     fun isSplitscreenExitTransition(transition: Transition): Boolean {
248         return transition.type == TransitionType.SPLIT_DISMISS ||
249             transition.type == TransitionType.SPLIT_DISMISS_SNAP
250     }
251 
252     fun isSplitscreenResizeTransition(transition: Transition): Boolean {
253         // This transition doesn't have a special type
254         return transition.type == TransitionType.CHANGE &&
255             transition.changes.size == 2 &&
256             transition.changes.all { change -> change.transitMode == TransitionType.CHANGE }
257     }
258 }
259