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