1/*
2 * Copyright 2017, 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
18import jsonProtoDefs from 'frameworks/base/core/proto/android/server/windowmanagertrace.proto'
19import jsonProtoLogDefs from 'ProtoLogSafePath/protolog.proto'
20import jsonProtoDefsSF from 'frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto'
21import jsonProtoDefsTrans from 'frameworks/native/cmds/surfacereplayer/proto/src/trace.proto'
22import jsonProtoDefsWL from 'WaylandSafePath/waylandtrace.proto'
23import protobuf from 'protobufjs'
24import { transform_layers, transform_layers_trace } from './transform_sf.js'
25import { transform_window_service, transform_window_trace } from './transform_wm.js'
26import { transform_transaction_trace } from './transform_transaction.js'
27import { transform_wl_outputstate, transform_wayland_trace } from './transform_wl.js'
28import { transform_protolog } from './transform_protolog.js'
29import { fill_transform_data } from './matrix_utils.js'
30import { mp4Decoder } from './decodeVideo.js'
31
32var protoDefs = protobuf.Root.fromJSON(jsonProtoDefs)
33  .addJSON(jsonProtoLogDefs.nested)
34  .addJSON(jsonProtoDefsSF.nested)
35  .addJSON(jsonProtoDefsTrans.nested)
36  .addJSON(jsonProtoDefsWL.nested);
37
38var WindowTraceMessage = protoDefs.lookupType(
39  "com.android.server.wm.WindowManagerTraceFileProto");
40var WindowMessage = protoDefs.lookupType(
41  "com.android.server.wm.WindowManagerServiceDumpProto");
42var LayersMessage = protoDefs.lookupType("android.surfaceflinger.LayersProto");
43var LayersTraceMessage = protoDefs.lookupType("android.surfaceflinger.LayersTraceFileProto");
44var TransactionMessage = protoDefs.lookupType("Trace");
45var WaylandMessage = protoDefs.lookupType("org.chromium.arc.wayland_composer.OutputStateProto");
46var WaylandTraceMessage = protoDefs.lookupType("org.chromium.arc.wayland_composer.TraceFileProto");
47var WindowLogMessage = protoDefs.lookupType(
48  "com.android.server.protolog.ProtoLogFileProto");
49var LogMessage = protoDefs.lookupType(
50  "com.android.server.protolog.ProtoLogMessage");
51
52const LAYER_TRACE_MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45] // .LYRTRACE
53const WINDOW_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45] // .WINTRACE
54const MPEG4_MAGIC_NMBER = [0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x6d, 0x70, 0x34, 0x32] // ....ftypmp42
55const WAYLAND_TRACE_MAGIC_NUMBER = [0x09, 0x57, 0x59, 0x4c, 0x54, 0x52, 0x41, 0x43, 0x45] // .WYLTRACE
56const PROTO_LOG_MAGIC_NUMBER = [0x09, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47] // .PROTOLOG
57
58const DATA_TYPES = {
59  WINDOW_MANAGER: {
60    name: "WindowManager",
61    icon: "view_compact",
62    mime: "application/octet-stream",
63  },
64  SURFACE_FLINGER: {
65    name: "SurfaceFlinger",
66    icon: "filter_none",
67    mime: "application/octet-stream",
68  },
69  SCREEN_RECORDING: {
70    name: "Screen recording",
71    icon: "videocam",
72    mime: "video/mp4",
73  },
74  TRANSACTION: {
75    name: "Transaction",
76    icon: "timeline",
77    mime: "application/octet-stream",
78  },
79  WAYLAND: {
80    name: "Wayland",
81    icon: "filter_none",
82    mime: "application/octet-stream",
83  },
84  PROTO_LOG: {
85    name: "ProtoLog",
86    icon: "notes",
87    mime: "application/octet-stream",
88  }
89}
90
91const FILE_TYPES = {
92  'window_trace': {
93    name: "WindowManager trace",
94    dataType: DATA_TYPES.WINDOW_MANAGER,
95    decoder: protoDecoder,
96    decoderParams: {
97      protoType: WindowTraceMessage,
98      transform: transform_window_trace,
99      timeline: true,
100    },
101  },
102  'layers_trace': {
103    name: "SurfaceFlinger trace",
104    dataType: DATA_TYPES.SURFACE_FLINGER,
105    decoder: protoDecoder,
106    decoderParams: {
107      protoType: LayersTraceMessage,
108      transform: transform_layers_trace,
109      timeline: true,
110    },
111  },
112  'wl_trace': {
113    name: "Wayland trace",
114    dataType: DATA_TYPES.WAYLAND,
115    decoder: protoDecoder,
116    decoderParams: {
117      protoType: WaylandTraceMessage,
118      transform: transform_wayland_trace,
119      timeline: true,
120    },
121  },
122  'layers_dump': {
123    name: "SurfaceFlinger dump",
124    dataType: DATA_TYPES.SURFACE_FLINGER,
125    decoder: protoDecoder,
126    decoderParams: {
127      protoType: LayersMessage,
128      transform: transform_layers,
129      timeline: false,
130    },
131  },
132  'window_dump': {
133    name: "WindowManager dump",
134    dataType: DATA_TYPES.WINDOW_MANAGER,
135    decoder: protoDecoder,
136    decoderParams: {
137      protoType: WindowMessage,
138      transform: transform_window_service,
139      timeline: false,
140    },
141  },
142  'wl_dump': {
143    name: "Wayland dump",
144    dataType: DATA_TYPES.WAYLAND,
145    decoder: protoDecoder,
146    decoderParams: {
147      protoType: WaylandMessage,
148      transform: transform_wl_outputstate,
149      timeline: false,
150    },
151  },
152  'screen_recording': {
153    name: "Screen recording",
154    dataType: DATA_TYPES.SCREEN_RECORDING,
155    decoder: videoDecoder,
156    decoderParams: {
157      videoDecoder: mp4Decoder,
158    },
159  },
160  'transaction': {
161    name: "Transaction",
162    dataType: DATA_TYPES.TRANSACTION,
163    decoder: protoDecoder,
164    decoderParams: {
165      protoType: TransactionMessage,
166      transform: transform_transaction_trace,
167      timeline: true,
168    }
169  },
170  'proto_log': {
171    name: "ProtoLog",
172    dataType: DATA_TYPES.PROTO_LOG,
173    decoder: protoDecoder,
174    decoderParams: {
175      protoType: WindowLogMessage,
176      transform: transform_protolog,
177      timeline: true,
178    }
179  }
180};
181
182// Replace enum values with string representation and
183// add default values to the proto objects. This function also handles
184// a special case with TransformProtos where the matrix may be derived
185// from the transform type.
186function modifyProtoFields(protoObj, displayDefaults) {
187  if (!protoObj || protoObj !== Object(protoObj) || !protoObj.$type) {
188    return;
189  }
190  for (var fieldName in protoObj.$type.fields) {
191    var fieldProperties = protoObj.$type.fields[fieldName];
192    var field = protoObj[fieldName];
193
194    if (Array.isArray(field)) {
195      field.forEach((item, _) => {
196        modifyProtoFields(item, displayDefaults);
197      })
198      continue;
199    }
200
201    if (displayDefaults && !(field)) {
202      protoObj[fieldName] = fieldProperties.defaultValue;
203    }
204
205    if (fieldProperties.type === 'TransformProto') {
206      fill_transform_data(protoObj[fieldName]);
207      continue;
208    }
209
210    if (fieldProperties.resolvedType && fieldProperties.resolvedType.valuesById) {
211      protoObj[fieldName] = fieldProperties.resolvedType.valuesById[protoObj[fieldProperties.name]];
212      continue;
213    }
214    modifyProtoFields(protoObj[fieldName], displayDefaults);
215  }
216}
217
218function protoDecoder(buffer, fileType, fileName, store) {
219  var decoded = fileType.decoderParams.protoType.decode(buffer);
220  modifyProtoFields(decoded, store.displayDefaults);
221  var transformed = fileType.decoderParams.transform(decoded);
222  var data
223  if (fileType.decoderParams.timeline) {
224    data = transformed.children;
225  } else {
226    data = [transformed];
227  }
228  let blobUrl = URL.createObjectURL(new Blob([buffer], { type: fileType.dataType.mime }));
229  return dataFile(fileName, data.map(x => x.timestamp), data, blobUrl, fileType.dataType);
230}
231
232function videoDecoder(buffer, fileType, fileName, store) {
233  let [data, timeline] = fileType.decoderParams.videoDecoder(buffer);
234  let blobUrl = URL.createObjectURL(new Blob([data], { type: fileType.dataType.mime }));
235  return dataFile(fileName, timeline, blobUrl, blobUrl, fileType.dataType);
236}
237
238function dataFile(filename, timeline, data, blobUrl, type) {
239  return {
240    filename: filename,
241    timeline: timeline,
242    data: data,
243    blobUrl: blobUrl,
244    type: type,
245    selectedIndex: 0,
246    destroy() {
247      URL.revokeObjectURL(this.blobUrl);
248    },
249  }
250}
251
252function arrayEquals(a, b) {
253  if (a.length !== b.length) {
254    return false;
255  }
256  for (var i = 0; i < a.length; i++) {
257    if (a[i] != b[i]) {
258      return false;
259    }
260  }
261  return true;
262}
263
264function arrayStartsWith(array, prefix) {
265  return arrayEquals(array.slice(0, prefix.length), prefix);
266}
267
268function decodedFile(fileType, buffer, fileName, store) {
269  return [fileType, fileType.decoder(buffer, fileType, fileName, store)];
270}
271
272function detectAndDecode(buffer, fileName, store) {
273  if (arrayStartsWith(buffer, LAYER_TRACE_MAGIC_NUMBER)) {
274    return decodedFile(FILE_TYPES['layers_trace'], buffer, fileName, store);
275  }
276  if (arrayStartsWith(buffer, WINDOW_TRACE_MAGIC_NUMBER)) {
277    return decodedFile(FILE_TYPES['window_trace'], buffer, fileName, store);
278  }
279  if (arrayStartsWith(buffer, MPEG4_MAGIC_NMBER)) {
280    return decodedFile(FILE_TYPES['screen_recording'], buffer, fileName, store);
281  }
282  if (arrayStartsWith(buffer, WAYLAND_TRACE_MAGIC_NUMBER)) {
283    return decodedFile(FILE_TYPES['wl_trace'], buffer, fileName, store);
284  }
285  if (arrayStartsWith(buffer, PROTO_LOG_MAGIC_NUMBER)) {
286    return decodedFile(FILE_TYPES['proto_log'], buffer, fileName, store);
287  }
288  for (var name of ['transaction', 'layers_dump', 'window_dump', 'wl_dump']) {
289    try {
290      return decodedFile(FILE_TYPES[name], buffer, fileName, store);
291    } catch (ex) {
292      // ignore exception and try next filetype
293    }
294  }
295  throw new Error('Unable to detect file');
296}
297
298export { detectAndDecode, DATA_TYPES, FILE_TYPES };
299