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