1 /* 2 * 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.traces.wm 18 19 import android.tools.Timestamp 20 import android.tools.Timestamps 21 import android.tools.TraceEntry 22 import android.tools.traces.surfaceflinger.LayersTrace 23 import android.tools.traces.surfaceflinger.Transaction 24 import android.tools.traces.surfaceflinger.TransactionsTrace 25 26 class Transition( 27 val id: Int, 28 val wmData: WmTransitionData = WmTransitionData(), 29 val shellData: ShellTransitionData = ShellTransitionData(), 30 ) : TraceEntry { 31 init { 32 require( 33 wmData.createTime != null || 34 wmData.sendTime != null || 35 wmData.abortTime != null || 36 wmData.finishTime != null || 37 wmData.startingWindowRemoveTime != null || 38 shellData.dispatchTime != null || 39 shellData.mergeRequestTime != null || 40 shellData.mergeTime != null || 41 shellData.abortTime != null <lambda>null42 ) { 43 "Transition requires at least one non-null timestamp" 44 } 45 } 46 47 override val timestamp = 48 wmData.createTime 49 ?: wmData.sendTime ?: shellData.dispatchTime ?: shellData.mergeRequestTime 50 ?: shellData.mergeTime ?: shellData.abortTime ?: wmData.finishTime 51 ?: wmData.abortTime ?: wmData.startingWindowRemoveTime 52 ?: error("Missing non-null timestamp") 53 54 val createTime: Timestamp = wmData.createTime ?: Timestamps.min() 55 56 val sendTime: Timestamp = wmData.sendTime ?: Timestamps.min() 57 58 val abortTime: Timestamp? = wmData.abortTime 59 60 val finishTime: Timestamp = wmData.finishTime ?: wmData.abortTime ?: Timestamps.max() 61 62 val startingWindowRemoveTime: Timestamp? = wmData.startingWindowRemoveTime 63 64 val dispatchTime: Timestamp = shellData.dispatchTime ?: Timestamps.min() 65 66 val mergeRequestTime: Timestamp? = shellData.mergeRequestTime 67 68 val mergeTime: Timestamp? = shellData.mergeTime 69 70 val shellAbortTime: Timestamp? = shellData.abortTime 71 72 val startTransactionId: Long = wmData.startTransactionId ?: -1L 73 74 val finishTransactionId: Long = wmData.finishTransactionId ?: -1L 75 76 val type: TransitionType = wmData.type ?: TransitionType.UNDEFINED 77 78 val changes: Collection<TransitionChange> = wmData.changes ?: emptyList() 79 80 val mergeTarget = shellData.mergeTarget 81 82 val handler = shellData.handler 83 84 val merged: Boolean = shellData.mergeTime != null 85 86 val played: Boolean = wmData.finishTime != null 87 88 val aborted: Boolean = wmData.abortTime != null || shellData.abortTime != null 89 getStartTransactionnull90 fun getStartTransaction(transactionsTrace: TransactionsTrace): Transaction? { 91 val matches = 92 transactionsTrace.allTransactions.filter { 93 it.id == this.startTransactionId || 94 it.mergedTransactionIds.contains(this.startTransactionId) 95 } 96 require(matches.size <= 1) { 97 "Too many transactions matches found for Transaction#${this.startTransactionId}." 98 } 99 return matches.firstOrNull() 100 } 101 getFinishTransactionnull102 fun getFinishTransaction(transactionsTrace: TransactionsTrace): Transaction? { 103 val matches = 104 transactionsTrace.allTransactions.filter { 105 it.id == this.finishTransactionId || 106 it.mergedTransactionIds.contains(this.finishTransactionId) 107 } 108 require(matches.size <= 1) { 109 "Too many transactions matches found for Transaction#${this.finishTransactionId}." 110 } 111 return matches.firstOrNull() 112 } 113 114 val isIncomplete: Boolean 115 get() = !played || aborted 116 mergenull117 fun merge(transition: Transition): Transition { 118 require(transition.mergeTarget == this.id) { 119 "Can't merge transition with mergedInto id ${transition.mergeTarget} " + 120 "into transition with id ${this.id}" 121 } 122 123 val finishTransition = 124 if (transition.finishTime > this.finishTime) { 125 transition 126 } else { 127 this 128 } 129 130 val mergedTransition = 131 Transition( 132 id = this.id, 133 wmData = 134 WmTransitionData( 135 createTime = wmData.createTime, 136 sendTime = wmData.sendTime, 137 abortTime = wmData.abortTime, 138 finishTime = finishTransition.wmData.finishTime, 139 startingWindowRemoveTime = wmData.startingWindowRemoveTime, 140 startTransactionId = wmData.startTransactionId, 141 finishTransactionId = finishTransition.wmData.finishTransactionId, 142 type = wmData.type, 143 changes = 144 (wmData.changes?.toMutableList() ?: mutableListOf()) 145 .apply { addAll(transition.wmData.changes ?: emptyList()) } 146 .toSet() 147 ), 148 shellData = shellData 149 ) 150 151 return mergedTransition 152 } 153 toStringnull154 override fun toString(): String = Formatter(null, null).format(this) 155 156 class Formatter(val layersTrace: LayersTrace?, val wmTrace: WindowManagerTrace?) { 157 private val changeFormatter = TransitionChange.Formatter(layersTrace, wmTrace) 158 159 fun format(transition: Transition): String = buildString { 160 appendLine("Transition#${transition.id}(") 161 appendLine("type=${transition.type},") 162 appendLine("handler=${transition.handler},") 163 appendLine("aborted=${transition.aborted},") 164 appendLine("played=${transition.played},") 165 appendLine("createTime=${transition.createTime},") 166 appendLine("sendTime=${transition.sendTime},") 167 appendLine("dispatchTime=${transition.dispatchTime},") 168 appendLine("mergeRequestTime=${transition.mergeRequestTime},") 169 appendLine("mergeTime=${transition.mergeTime},") 170 appendLine("shellAbortTime=${transition.shellAbortTime},") 171 appendLine("finishTime=${transition.finishTime},") 172 appendLine("startingWindowRemoveTime=${transition.startingWindowRemoveTime},") 173 appendLine("startTransactionId=${transition.startTransactionId},") 174 appendLine("finishTransactionId=${transition.finishTransactionId},") 175 appendLine("mergedInto=${transition.mergeTarget}") 176 appendLine("changes=[") 177 appendLine( 178 transition.changes 179 .joinToString(",\n") { changeFormatter.format(it) } 180 .prependIndent() 181 ) 182 appendLine("]") 183 appendLine(")") 184 } 185 } 186 187 companion object { mergePartialTransitionsnull188 fun mergePartialTransitions(transition1: Transition, transition2: Transition): Transition { 189 require(transition1.id == transition2.id) { 190 "Can't merge transitions with mismatching ids" 191 } 192 193 return Transition( 194 id = transition1.id, 195 transition1.wmData.merge(transition2.wmData), 196 transition1.shellData.merge(transition2.shellData) 197 ) 198 } 199 } 200 } 201