• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.common.flicker.extractors
18 
19 import android.tools.common.flicker.extractors.TransitionTransforms.inCujRangeFilter
20 import android.tools.common.flicker.extractors.TransitionTransforms.mergeTrampolineTransitions
21 import android.tools.common.flicker.extractors.TransitionTransforms.noOpTransitionsTransform
22 import android.tools.common.flicker.extractors.TransitionTransforms.permissionDialogFilter
23 import android.tools.common.io.Reader
24 import android.tools.common.traces.events.Cuj
25 import android.tools.common.traces.wm.Transition
26 import android.tools.common.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     // Transformations applied, in order, to all transitions the reader returns to end up with the
36     // targeted transition.
37     private val transforms: List<TransitionsTransform> =
38         listOf(
39             mainTransform,
40             inCujRangeFilter,
41             permissionDialogFilter,
42             mergeTrampolineTransitions,
43             finalTransform
44         )
45 ) : TransitionMatcher {
46     override fun getMatches(reader: Reader, cujEntry: Cuj): Collection<Transition> {
47         val transitionsTrace = reader.readTransitionsTrace() ?: error("Missing transitions trace")
48 
49         val completeTransitions = transitionsTrace.entries.filter { !it.isIncomplete }
50 
51         val formattedTransitions = {
52             "[\n" +
53                 "${
54                         transitionsTrace.entries.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: ${formattedTransitions()}"
64         }
65 
66         var appliedTransformsCount = 0
67         val matchedTransitions =
68             transforms.fold(completeTransitions) { transitions, transform ->
69                 val remainingTransitions = transform(transitions, cujEntry, reader)
70 
71                 appliedTransformsCount++
72 
73                 require(!associatedTransitionRequired || remainingTransitions.isNotEmpty()) {
74                     "Required an associated transition for ${cujEntry.cuj.name}" +
75                         "(${cujEntry.startTimestamp},${cujEntry.endTimestamp}) " +
76                         "but no transition left after $appliedTransformsCount/${transforms.size} " +
77                         "filters from: ${formattedTransitions()}!"
78                 }
79 
80                 remainingTransitions
81             }
82 
83         require(!associatedTransitionRequired || matchedTransitions.size == 1) {
84             "Got too many associated transitions expected only 1."
85         }
86 
87         return matchedTransitions
88     }
89 }
90 
91 object TransitionTransforms {
cujEntrynull92     val inCujRangeFilter: TransitionsTransform = { transitions, cujEntry, reader ->
93         transitions.filter { transition ->
94             val transitionCreatedWithinCujTags =
95                 cujEntry.startTimestamp <= transition.createTime &&
96                     transition.createTime <= cujEntry.endTimestamp
97 
98             val transitionSentWithinCujTags =
99                 cujEntry.startTimestamp <= transition.sendTime &&
100                     transition.sendTime <= cujEntry.endTimestamp
101 
102             val transactionsTrace =
103                 reader.readTransactionsTrace() ?: error("Missing transactions trace")
104             val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
105             val finishTransaction = transition.getFinishTransaction(transactionsTrace)
106             val transitionEndTimestamp =
107                 if (finishTransaction != null) {
108                     layersTrace.getEntryForTransaction(finishTransaction).timestamp
109                 } else {
110                     transition.finishTime
111                 }
112             val cujStartsDuringTransition =
113                 transition.sendTime <= cujEntry.startTimestamp &&
114                     cujEntry.startTimestamp <= transitionEndTimestamp
115             return@filter transitionCreatedWithinCujTags ||
116                 transitionSentWithinCujTags ||
117                 cujStartsDuringTransition
118         }
119     }
120 
readernull121     val permissionDialogFilter: TransitionsTransform = { transitions, _, reader ->
122         transitions.filter { !isPermissionDialogOpenTransition(it, reader) }
123     }
124 
readernull125     val mergeTrampolineTransitions: TransitionsTransform = { transitions, _, reader ->
126         require(transitions.size <= 2) {
127             "Got to merging trampoline transitions with more than 2 transitions left :: " +
128                 transitions.joinToString {
129                     Transition.Formatter(reader.readLayersTrace(), reader.readWmTrace()).format(it)
130                 }
131         }
132         if (
133             transitions.size == 2 &&
134                 isTrampolinedOpenTransition(transitions[0], transitions[1], reader)
135         ) {
136             // Remove the trampoline transition
137             listOf(transitions[0])
138         } else {
139             transitions
140         }
141     }
142 
transitionsnull143     val noOpTransitionsTransform: TransitionsTransform = { transitions, _, _ -> transitions }
144 
isPermissionDialogOpenTransitionnull145     private fun isPermissionDialogOpenTransition(transition: Transition, reader: Reader): Boolean {
146         if (transition.changes.size != 1) {
147             return false
148         }
149 
150         val change = transition.changes[0]
151         if (transition.type != TransitionType.OPEN || change.transitMode != TransitionType.OPEN) {
152             return false
153         }
154 
155         val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
156         val layers =
157             layersTrace.entries.flatMap { it.flattenedLayers.asList() }.distinctBy { it.id }
158 
159         val candidateLayer =
160             layers.firstOrNull { it.id == change.layerId }
161                 ?: error("Open layer from $transition not found in layers trace")
162         return candidateLayer.name.contains("permissioncontroller")
163     }
164 
isTrampolinedOpenTransitionnull165     private fun isTrampolinedOpenTransition(
166         firstTransition: Transition,
167         secondTransition: Transition,
168         reader: Reader
169     ): Boolean {
170         val candidateTaskLayers =
171             firstTransition.changes
172                 .filter {
173                     it.transitMode == TransitionType.OPEN ||
174                         it.transitMode == TransitionType.TO_FRONT
175                 }
176                 .map { it.layerId }
177         if (candidateTaskLayers.isEmpty()) {
178             return false
179         }
180 
181         require(candidateTaskLayers.size == 1) {
182             "Unhandled case (more than 1 task candidate) in isTrampolinedOpenTransition()"
183         }
184 
185         val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace")
186         val layers =
187             layersTrace.entries.flatMap { it.flattenedLayers.asList() }.distinctBy { it.id }
188 
189         val candidateTaskLayerId = candidateTaskLayers[0]
190         val candidateTaskLayer = layers.first { it.id == candidateTaskLayerId }
191         if (!candidateTaskLayer.name.contains("Task")) {
192             return false
193         }
194 
195         val candidateTrampolinedActivities =
196             secondTransition.changes
197                 .filter { it.transitMode == TransitionType.CLOSE }
198                 .map { it.layerId }
199         val candidateTargetActivities =
200             secondTransition.changes
201                 .filter {
202                     it.transitMode == TransitionType.OPEN ||
203                         it.transitMode == TransitionType.TO_FRONT
204                 }
205                 .map { it.layerId }
206         if (candidateTrampolinedActivities.isEmpty() || candidateTargetActivities.isEmpty()) {
207             return false
208         }
209 
210         require(candidateTargetActivities.size == 1) {
211             "Unhandled case (more than 1 trampolined candidate) in " +
212                 "isTrampolinedOpenTransition()"
213         }
214         require(candidateTargetActivities.size == 1) {
215             "Unhandled case (more than 1 target candidate) in isTrampolinedOpenTransition()"
216         }
217 
218         val candidateTrampolinedActivityId = candidateTargetActivities[0]
219         val candidateTrampolinedActivity = layers.first { it.id == candidateTrampolinedActivityId }
220         if (candidateTrampolinedActivity.parent?.id != candidateTaskLayerId) {
221             return false
222         }
223 
224         val candidateTargetActivityId = candidateTargetActivities[0]
225         val candidateTargetActivity = layers.first { it.id == candidateTargetActivityId }
226         if (candidateTargetActivity.parent?.id != candidateTaskLayerId) {
227             return false
228         }
229 
230         return true
231     }
232 }
233