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 // Fullscreen: 1 app + 1 recents 99 // SplitScreen: 2 apps + 2 split containers + 1 split root + 1 recents 100 (closingAppLayers.count() == 2 || closingAppLayers.count() == 6) 101 } 102 103 var quickswitchFromLauncherTransitions = 104 artificiallyMergedTransitions.filter { transition -> 105 val openingAppLayers = 106 transition.changes.filter { 107 it.transitMode == TO_FRONT && 108 isAppTransitionChange(it, layersTrace, wmTrace) 109 } 110 111 transition.handler == TransitionHandler.DEFAULT && 112 (openingAppLayers.count() == 1 || openingAppLayers.count() == 5) && 113 transition.changes.count { 114 it.transitMode == TO_BACK && 115 isLauncherTopLevelTaskLayer(it.layerId, layersTrace) 116 } == 1 117 } 118 119 // TODO: (b/300068479) temporary work around to ensure transition is associated with CUJ 120 val hundredMs = CrossPlatform.timestamp.from(elapsedNanos = 100000000L) 121 122 quickswitchFromLauncherTransitions = 123 quickswitchFromLauncherTransitions.map { 124 // We create the transition right about the same time we end the CUJ tag 125 val createTimeAdjustedForTolerance = it.wmData.createTime?.minus(hundredMs) 126 Transition( 127 id = it.id, 128 wmData = 129 it.wmData.merge( 130 WmTransitionData( 131 createTime = createTimeAdjustedForTolerance, 132 sendTime = createTimeAdjustedForTolerance, 133 ) 134 ), 135 shellData = it.shellData, 136 ) 137 } 138 139 quickswitchBetweenAppsTransitions + quickswitchFromLauncherTransitions 140 } 141 142 val QUICK_SWITCH_TRANSITION_POST_PROCESSING: TransitionsTransform = { transitions, _, reader -> 143 require(transitions.size == 1) { "Expected 1 transition but got ${transitions.size}" } 144 145 val transition = transitions.first() 146 147 val layersTrace = reader.readLayersTrace() ?: error("Missing layers trace") 148 val wallpaperId = 149 transition.changes 150 .map { it.layerId } 151 .firstOrNull { isWallpaperTokenLayer(it, layersTrace) } 152 val isSwitchFromLauncher = wallpaperId == null 153 val launcherId = 154 if (isSwitchFromLauncher) null 155 else 156 transition.changes 157 .map { it.layerId } 158 .firstOrNull { isLauncherTopLevelTaskLayer(it, layersTrace) } 159 ?: error("Missing launcher layer in transition") 160 161 val filteredChanges = 162 transition.changes.filter { it.layerId != wallpaperId && it.layerId != launcherId } 163 164 val closingAppChange = filteredChanges.first { it.transitMode == TO_BACK } 165 val openingAppChange = filteredChanges.first { it.transitMode == TO_FRONT } 166 167 // Transition removing the intermediate launcher changes 168 listOf( 169 Transition( 170 transition.id, 171 WmTransitionData( 172 createTime = transition.wmData.createTime, 173 sendTime = transition.wmData.sendTime, 174 abortTime = transition.wmData.abortTime, 175 finishTime = transition.wmData.finishTime, 176 startingWindowRemoveTime = transition.wmData.startingWindowRemoveTime, 177 startTransactionId = transition.wmData.startTransactionId, 178 finishTransactionId = transition.wmData.finishTransactionId, 179 type = transition.wmData.type, 180 changes = listOf(closingAppChange, openingAppChange), 181 ), 182 transition.shellData, 183 ) 184 ) 185 } 186 187 private fun isLauncherTopLevelTaskLayer(layerId: Int, layersTrace: LayersTrace): Boolean { 188 return layersTrace.entries.any { entry -> 189 val launcherLayer = 190 entry.flattenedLayers.firstOrNull { layer -> 191 ComponentNameMatcher.LAUNCHER.or(ComponentNameMatcher.AOSP_LAUNCHER) 192 .layerMatchesAnyOf(layer) 193 } ?: return@any false 194 195 var curLayer = launcherLayer 196 while (!curLayer.isTask && curLayer.parent != null) { 197 curLayer = curLayer.parent ?: error("unreachable") 198 } 199 if (!curLayer.isTask) { 200 error("Expected a task layer above the launcher layer") 201 } 202 203 var launcherTopLevelTaskLayer = curLayer 204 // Might have nested task layers 205 while ( 206 launcherTopLevelTaskLayer.parent != null && 207 launcherTopLevelTaskLayer.parent!!.isTask 208 ) { 209 launcherTopLevelTaskLayer = launcherTopLevelTaskLayer.parent ?: error("unreachable") 210 } 211 212 return@any launcherTopLevelTaskLayer.id == layerId 213 } 214 } 215 216 private fun isWallpaperTokenLayer(layerId: Int, layersTrace: LayersTrace): Boolean { 217 return layersTrace.entries.any { entry -> 218 entry.flattenedLayers.any { layer -> 219 layer.id == layerId && 220 ComponentNameMatcher.WALLPAPER_WINDOW_TOKEN.layerMatchesAnyOf(layer) 221 } 222 } 223 } 224 225 val APP_CLOSE_TO_PIP_TRANSITION_FILTER: TransitionsTransform = { ts, _, _ -> 226 ts.filter { it.type == TransitionType.PIP } 227 } 228 229 val ENTER_SPLIT_SCREEN_MATCHER = 230 object : ITransitionMatcher { 231 override fun findAll(transitions: Collection<Transition>): Collection<Transition> { 232 return transitions.filter { isSplitscreenEnterTransition(it) } 233 } 234 } 235 236 val EXIT_SPLIT_SCREEN_FILTER: TransitionsTransform = { ts, _, _ -> 237 ts.filter { isSplitscreenExitTransition(it) } 238 } 239 240 val RESIZE_SPLIT_SCREEN_FILTER: TransitionsTransform = { ts, _, _ -> 241 ts.filter { isSplitscreenResizeTransition(it) } 242 } 243 244 fun isSplitscreenEnterTransition(transition: Transition): Boolean { 245 return transition.handler == SPLIT_SCREEN_TRANSITION_HANDLER && transition.type == TO_FRONT 246 } 247 248 fun isSplitscreenExitTransition(transition: Transition): Boolean { 249 return transition.type == TransitionType.SPLIT_DISMISS || 250 transition.type == TransitionType.SPLIT_DISMISS_SNAP 251 } 252 253 fun isSplitscreenResizeTransition(transition: Transition): Boolean { 254 // This transition doesn't have a special type 255 return transition.type == TransitionType.CHANGE && 256 transition.changes.size == 2 && 257 transition.changes.all { change -> change.transitMode == TransitionType.CHANGE } 258 } 259 } 260