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.extractors
18 
19 import android.tools.flicker.extractors.TransitionTransforms.inCujRangeFilter
20 import android.tools.flicker.extractors.TransitionTransforms.mergeTrampolineTransitions
21 import android.tools.flicker.extractors.TransitionTransforms.noOpTransitionsTransform
22 import android.tools.flicker.extractors.TransitionTransforms.permissionDialogFilter
23 import android.tools.io.Reader
24 import android.tools.traces.events.Cuj
25 import android.tools.traces.wm.Transition
26 import android.tools.traces.wm.TransitionType
27 
28 typealias TransitionsTransform =
29     (transitions: List<Transition>, cujEntry: Cuj, reader: Reader) -> List<Transition>
30 
31 class TaggedCujTransitionMatcher(
32     private val mainTransform: TransitionsTransform = noOpTransitionsTransform,
33     private val finalTransform: TransitionsTransform = noOpTransitionsTransform,
34     private val associatedTransitionRequired: Boolean = true,
35 ) : TransitionMatcher {
36     override fun getMatches(reader: Reader, cujEntry: Cuj): Collection<Transition> {
37         val transformsToNames: Map<TransitionsTransform, String> =
38             mapOf(
39                 mainTransform to "Main Transform",
40                 inCujRangeFilter to "In CUJ Range Filter",
41                 permissionDialogFilter to "Permission Dialog Filter",
42                 mergeTrampolineTransitions to "Merge Trampoline Transitions",
43                 finalTransform to "Final Transform",
44             )
45         val transforms = transformsToNames.keys
46 
47         val transitionsTrace = reader.readTransitionsTrace() ?: error("Missing transitions trace")
48 
49         val completeTransitions = transitionsTrace.entries.filter { !it.isIncomplete }
50 
51         val formattedTransitions = { transitions: Collection<Transition> ->
52             "[\n" +
53                 "${
54                     transitions.joinToString(",\n") {
55                             Transition.Formatter(reader.readLayersTrace(),
56                                     reader.readWmTrace()).format(it)
57                         }.prependIndent()
58                     }\n" +
59                 "]"
60         }
61 
62         require(!associatedTransitionRequired || completeTransitions.isNotEmpty()) {
63             "No successfully finished transitions in: " +
64                 formattedTransitions(transitionsTrace.entries)
65         }
66 
67         var appliedTransformsCount = 0
68         val matchedTransitions =
69             transforms.fold(completeTransitions) { transitions, transform ->
70                 val remainingTransitions =
71                     try {
72                         transform(transitions, cujEntry, reader)
73                     } catch (e: Exception) {
74                         throw RuntimeException(
75                             "Failed to apply ${transformsToNames[transform]} on " +
76                                 "the following transitions (CUJ=${cujEntry.cuj.name}" +
77                                 "[${cujEntry.startTimestamp},${cujEntry.endTimestamp}]):\n " +
78                                 formattedTransitions(transitions),
79                             e
80                         )
81                     }
82 
83                 appliedTransformsCount++
84 
85                 require(!associatedTransitionRequired || remainingTransitions.isNotEmpty()) {
86                     "Required an associated transition for ${cujEntry.cuj.name}" +
87                         "(${cujEntry.startTimestamp},${cujEntry.endTimestamp}) " +
88                         "but no transition left after applying ${transformsToNames[transform]} " +
89                         "($appliedTransformsCount/${transforms.size} filters) " +
90                         "from the following transitions: " +
91                         formattedTransitions(transitionsTrace.entries)
92                 }
93 
94                 remainingTransitions
95             }
96 
97         require(!associatedTransitionRequired || matchedTransitions.size == 1) {
98             "Got too many associated transitions for CUJ $cujEntry expected only 1, but got: [\n" +
99                 "${matchedTransitions.joinToString(",\n")}\n]"
100         }
101 
102         return matchedTransitions
103     }
104 }
105 
106 object TransitionTransforms {
cujEntrynull107     val inCujRangeFilter: TransitionsTransform = { transitions, cujEntry, reader ->
108         transitions.filter { transition ->
109             val transitionCreatedWithinCujTags =
110                 cujEntry.startTimestamp <= transition.createTime &&
111                     transition.createTime <= cujEntry.endTimestamp
112 
113             val transitionSentWithinCujTags =
114                 cujEntry.startTimestamp <= transition.sendTime &&
115                     transition.sendTime <= cujEntry.endTimestamp
116 
117             val transactionsTrace =
118                 reader.readTransactionsTrace() ?: error("Missing transactions trace")
119             val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
120             val finishTransaction = transition.getFinishTransaction(transactionsTrace)
121             val transitionEndTimestamp =
122                 if (finishTransaction != null) {
123                     layersTrace.getEntryForTransaction(finishTransaction).timestamp
124                 } else {
125                     transition.finishTime
126                 }
127             val cujStartsDuringTransition =
128                 transition.sendTime <= cujEntry.startTimestamp &&
129                     cujEntry.startTimestamp <= transitionEndTimestamp
130             return@filter transitionCreatedWithinCujTags ||
131                 transitionSentWithinCujTags ||
132                 cujStartsDuringTransition
133         }
134     }
135 
readernull136     val permissionDialogFilter: TransitionsTransform = { transitions, _, reader ->
137         transitions.filter { !isPermissionDialogOpenTransition(it, reader) }
138     }
139 
readernull140     val mergeTrampolineTransitions: TransitionsTransform = { transitions, _, reader ->
141         require(transitions.size <= 2) {
142             "Got to merging trampoline transitions with more than 2 transitions left :: " +
143                 transitions.joinToString {
144                     Transition.Formatter(reader.readLayersTrace(), reader.readWmTrace()).format(it)
145                 }
146         }
147         if (
148             transitions.size == 2 &&
149                 isTrampolinedOpenTransition(
150                     transitions.first(),
151                     transitions.drop(1).first(),
152                     reader
153                 )
154         ) {
155             // Remove the trampoline transition
156             listOf(transitions.first())
157         } else {
158             transitions
159         }
160     }
161 
transitionsnull162     val noOpTransitionsTransform: TransitionsTransform = { transitions, _, _ -> transitions }
163 
isPermissionDialogOpenTransitionnull164     private fun isPermissionDialogOpenTransition(transition: Transition, reader: Reader): Boolean {
165         if (transition.changes.size != 1) {
166             return false
167         }
168 
169         val change = transition.changes.first()
170         if (transition.type != TransitionType.OPEN || change.transitMode != TransitionType.OPEN) {
171             return false
172         }
173 
174         val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
175         val layers = layersTrace.entries.flatMap { it.flattenedLayers }.distinctBy { it.id }
176 
177         val candidateLayer =
178             layers.firstOrNull { it.id == change.layerId }
179                 ?: error("Open layer from $transition not found in layers trace")
180         return candidateLayer.name.contains("permissioncontroller")
181     }
182 
isTrampolinedOpenTransitionnull183     private fun isTrampolinedOpenTransition(
184         firstTransition: Transition,
185         secondTransition: Transition,
186         reader: Reader
187     ): Boolean {
188         val candidateTaskLayers =
189             firstTransition.changes
190                 .filter {
191                     it.transitMode == TransitionType.OPEN ||
192                         it.transitMode == TransitionType.TO_FRONT
193                 }
194                 .map { it.layerId }
195         if (candidateTaskLayers.isEmpty()) {
196             return false
197         }
198 
199         require(candidateTaskLayers.size == 1) {
200             "Unhandled case (more than 1 task candidate) in isTrampolinedOpenTransition()"
201         }
202 
203         val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
204         val layers = layersTrace.entries.flatMap { it.flattenedLayers }.distinctBy { it.id }
205 
206         val candidateTaskLayerId = candidateTaskLayers.first()
207         val candidateTaskLayer = layers.first { it.id == candidateTaskLayerId }
208         if (!candidateTaskLayer.name.contains("Task")) {
209             return false
210         }
211 
212         val candidateTrampolinedActivities =
213             secondTransition.changes
214                 .filter { it.transitMode == TransitionType.CLOSE }
215                 .map { it.layerId }
216         val candidateTargetActivities =
217             secondTransition.changes
218                 .filter {
219                     it.transitMode == TransitionType.OPEN ||
220                         it.transitMode == TransitionType.TO_FRONT
221                 }
222                 .map { it.layerId }
223         if (candidateTrampolinedActivities.isEmpty() || candidateTargetActivities.isEmpty()) {
224             return false
225         }
226 
227         require(candidateTargetActivities.size == 1) {
228             "Unhandled case (more than 1 trampolined candidate) in " +
229                 "isTrampolinedOpenTransition()"
230         }
231         require(candidateTargetActivities.size == 1) {
232             "Unhandled case (more than 1 target candidate) in isTrampolinedOpenTransition()"
233         }
234 
235         val candidateTrampolinedActivityId = candidateTargetActivities.first()
236         val candidateTrampolinedActivity = layers.first { it.id == candidateTrampolinedActivityId }
237         if (candidateTrampolinedActivity.parent?.id != candidateTaskLayerId) {
238             return false
239         }
240 
241         val candidateTargetActivityId = candidateTargetActivities.first()
242         val candidateTargetActivity = layers.first { it.id == candidateTargetActivityId }
243         if (candidateTargetActivity.parent?.id != candidateTaskLayerId) {
244             return false
245         }
246 
247         return true
248     }
249 }
250