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