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