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