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 50 ?: shellData.dispatchTime 51 ?: shellData.mergeRequestTime 52 ?: shellData.mergeTime 53 ?: shellData.abortTime 54 ?: wmData.finishTime 55 ?: wmData.abortTime 56 ?: wmData.startingWindowRemoveTime 57 ?: error("Missing non-null timestamp") 58 59 val createTime: Timestamp = wmData.createTime ?: Timestamps.min() 60 61 val sendTime: Timestamp = wmData.sendTime ?: Timestamps.min() 62 63 val abortTime: Timestamp? = wmData.abortTime 64 65 val finishTime: Timestamp = wmData.finishTime ?: wmData.abortTime ?: Timestamps.max() 66 67 val startingWindowRemoveTime: Timestamp? = wmData.startingWindowRemoveTime 68 69 val dispatchTime: Timestamp = shellData.dispatchTime ?: Timestamps.min() 70 71 val mergeRequestTime: Timestamp? = shellData.mergeRequestTime 72 73 val mergeTime: Timestamp? = shellData.mergeTime 74 75 val shellAbortTime: Timestamp? = shellData.abortTime 76 77 val startTransactionId: Long = wmData.startTransactionId ?: -1L 78 79 val finishTransactionId: Long = wmData.finishTransactionId ?: -1L 80 81 val type: TransitionType = wmData.type ?: TransitionType.UNDEFINED 82 83 val changes: Collection<TransitionChange> = wmData.changes ?: emptyList() 84 85 val mergeTarget = shellData.mergeTarget 86 87 val handler = shellData.handler 88 89 val merged: Boolean = shellData.mergeTime != null 90 91 val played: Boolean = wmData.finishTime != null 92 93 val aborted: Boolean = wmData.abortTime != null || shellData.abortTime != null 94 getStartTransactionnull95 fun getStartTransaction(transactionsTrace: TransactionsTrace): Transaction? { 96 val matches = 97 transactionsTrace.allTransactions.filter { 98 it.id == this.startTransactionId || 99 it.mergedTransactionIds.contains(this.startTransactionId) 100 } 101 require(matches.size <= 1) { 102 "Too many transactions matches found for Transaction#${this.startTransactionId}." 103 } 104 return matches.firstOrNull() 105 } 106 getFinishTransactionnull107 fun getFinishTransaction(transactionsTrace: TransactionsTrace): Transaction? { 108 val matches = 109 transactionsTrace.allTransactions.filter { 110 it.id == this.finishTransactionId || 111 it.mergedTransactionIds.contains(this.finishTransactionId) 112 } 113 require(matches.size <= 1) { 114 "Too many transactions matches found for Transaction#${this.finishTransactionId}." 115 } 116 return matches.firstOrNull() 117 } 118 119 val isIncomplete: Boolean 120 get() = !played || aborted 121 mergenull122 fun merge(transition: Transition): Transition { 123 require(transition.mergeTarget == this.id) { 124 "Can't merge transition with mergedInto id ${transition.mergeTarget} " + 125 "into transition with id ${this.id}" 126 } 127 128 val finishTransition = 129 if (transition.finishTime > this.finishTime) { 130 transition 131 } else { 132 this 133 } 134 135 val mergedTransition = 136 Transition( 137 id = this.id, 138 wmData = 139 WmTransitionData( 140 createTime = wmData.createTime, 141 sendTime = wmData.sendTime, 142 abortTime = wmData.abortTime, 143 finishTime = finishTransition.wmData.finishTime, 144 startingWindowRemoveTime = wmData.startingWindowRemoveTime, 145 startTransactionId = wmData.startTransactionId, 146 finishTransactionId = finishTransition.wmData.finishTransactionId, 147 type = wmData.type, 148 changes = 149 (wmData.changes?.toMutableList() ?: mutableListOf()) 150 .apply { addAll(transition.wmData.changes ?: emptyList()) } 151 .toSet(), 152 ), 153 shellData = shellData, 154 ) 155 156 return mergedTransition 157 } 158 toStringnull159 override fun toString(): String = Formatter(null, null).format(this) 160 161 class Formatter(val layersTrace: LayersTrace?, val wmTrace: WindowManagerTrace?) { 162 private val changeFormatter = TransitionChange.Formatter(layersTrace, wmTrace) 163 164 fun format(transition: Transition): String = buildString { 165 appendLine("Transition#${transition.id}(") 166 appendLine("type=${transition.type},") 167 appendLine("handler=${transition.handler},") 168 appendLine("aborted=${transition.aborted},") 169 appendLine("played=${transition.played},") 170 appendLine("createTime=${transition.createTime},") 171 appendLine("sendTime=${transition.sendTime},") 172 appendLine("dispatchTime=${transition.dispatchTime},") 173 appendLine("mergeRequestTime=${transition.mergeRequestTime},") 174 appendLine("mergeTime=${transition.mergeTime},") 175 appendLine("shellAbortTime=${transition.shellAbortTime},") 176 appendLine("finishTime=${transition.finishTime},") 177 appendLine("startingWindowRemoveTime=${transition.startingWindowRemoveTime},") 178 appendLine("startTransactionId=${transition.startTransactionId},") 179 appendLine("finishTransactionId=${transition.finishTransactionId},") 180 appendLine("mergedInto=${transition.mergeTarget}") 181 appendLine("changes=[") 182 appendLine( 183 transition.changes 184 .joinToString(",\n") { changeFormatter.format(it) } 185 .prependIndent() 186 ) 187 appendLine("]") 188 appendLine(")") 189 } 190 } 191 192 companion object { mergePartialTransitionsnull193 fun mergePartialTransitions(transition1: Transition, transition2: Transition): Transition { 194 require(transition1.id == transition2.id) { 195 "Can't merge transitions with mismatching ids" 196 } 197 198 return Transition( 199 id = transition1.id, 200 transition1.wmData.merge(transition2.wmData), 201 transition1.shellData.merge(transition2.shellData), 202 ) 203 } 204 } 205 } 206