xref: /aosp_15_r20/development/tools/winscope/src/app/timeline_data.ts (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker/*
2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2022 The Android Open Source Project
3*90c8c64dSAndroid Build Coastguard Worker *
4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*90c8c64dSAndroid Build Coastguard Worker *
8*90c8c64dSAndroid Build Coastguard Worker *      http://www.apache.org/licenses/LICENSE-2.0
9*90c8c64dSAndroid Build Coastguard Worker *
10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License.
15*90c8c64dSAndroid Build Coastguard Worker */
16*90c8c64dSAndroid Build Coastguard Worker
17*90c8c64dSAndroid Build Coastguard Workerimport {TimeRange, Timestamp} from 'common/time';
18*90c8c64dSAndroid Build Coastguard Workerimport {ComponentTimestampConverter} from 'common/timestamp_converter';
19*90c8c64dSAndroid Build Coastguard Workerimport {UserNotifier} from 'common/user_notifier';
20*90c8c64dSAndroid Build Coastguard Workerimport {CannotParseAllTransitions} from 'messaging/user_warnings';
21*90c8c64dSAndroid Build Coastguard Workerimport {ScreenRecordingUtils} from 'trace/screen_recording_utils';
22*90c8c64dSAndroid Build Coastguard Workerimport {Trace, TraceEntry} from 'trace/trace';
23*90c8c64dSAndroid Build Coastguard Workerimport {Traces} from 'trace/traces';
24*90c8c64dSAndroid Build Coastguard Workerimport {TraceEntryFinder} from 'trace/trace_entry_finder';
25*90c8c64dSAndroid Build Coastguard Workerimport {TracePosition} from 'trace/trace_position';
26*90c8c64dSAndroid Build Coastguard Workerimport {TraceType, TraceTypeUtils} from 'trace/trace_type';
27*90c8c64dSAndroid Build Coastguard Workerimport {PropertyTreeNode} from 'trace/tree_node/property_tree_node';
28*90c8c64dSAndroid Build Coastguard Worker
29*90c8c64dSAndroid Build Coastguard Workerexport class TimelineData {
30*90c8c64dSAndroid Build Coastguard Worker  private traces = new Traces();
31*90c8c64dSAndroid Build Coastguard Worker  private screenRecordingVideo?: Blob;
32*90c8c64dSAndroid Build Coastguard Worker  private firstEntry?: TraceEntry<object>;
33*90c8c64dSAndroid Build Coastguard Worker  private lastEntry?: TraceEntry<object>;
34*90c8c64dSAndroid Build Coastguard Worker  private explicitlySetPosition?: TracePosition;
35*90c8c64dSAndroid Build Coastguard Worker  private explicitlySetSelection?: TimeRange;
36*90c8c64dSAndroid Build Coastguard Worker  private explicitlySetZoomRange?: TimeRange;
37*90c8c64dSAndroid Build Coastguard Worker  private lastReturnedCurrentPosition?: TracePosition;
38*90c8c64dSAndroid Build Coastguard Worker  private lastReturnedFullTimeRange?: TimeRange;
39*90c8c64dSAndroid Build Coastguard Worker  private lastReturnedCurrentEntries = new Map<
40*90c8c64dSAndroid Build Coastguard Worker    Trace<object>,
41*90c8c64dSAndroid Build Coastguard Worker    TraceEntry<any> | undefined
42*90c8c64dSAndroid Build Coastguard Worker  >();
43*90c8c64dSAndroid Build Coastguard Worker  private activeTrace: Trace<object> | undefined;
44*90c8c64dSAndroid Build Coastguard Worker  private transitionEntries: Array<PropertyTreeNode | undefined> = []; // cached trace entries to avoid TP and object creation latencies each time transition timeline is redrawn
45*90c8c64dSAndroid Build Coastguard Worker  private timestampConverter: ComponentTimestampConverter | undefined;
46*90c8c64dSAndroid Build Coastguard Worker
47*90c8c64dSAndroid Build Coastguard Worker  async initialize(
48*90c8c64dSAndroid Build Coastguard Worker    traces: Traces,
49*90c8c64dSAndroid Build Coastguard Worker    screenRecordingVideo: Blob | undefined,
50*90c8c64dSAndroid Build Coastguard Worker    timestampConverter: ComponentTimestampConverter,
51*90c8c64dSAndroid Build Coastguard Worker  ) {
52*90c8c64dSAndroid Build Coastguard Worker    this.clear();
53*90c8c64dSAndroid Build Coastguard Worker
54*90c8c64dSAndroid Build Coastguard Worker    this.timestampConverter = timestampConverter;
55*90c8c64dSAndroid Build Coastguard Worker
56*90c8c64dSAndroid Build Coastguard Worker    this.traces = new Traces();
57*90c8c64dSAndroid Build Coastguard Worker    traces.forEachTrace((trace, type) => {
58*90c8c64dSAndroid Build Coastguard Worker      // Filter out empty traces or dumps with invalid timestamp (would mess up the timeline)
59*90c8c64dSAndroid Build Coastguard Worker      if (trace.lengthEntries === 0 || trace.isDumpWithoutTimestamp()) {
60*90c8c64dSAndroid Build Coastguard Worker        return;
61*90c8c64dSAndroid Build Coastguard Worker      }
62*90c8c64dSAndroid Build Coastguard Worker
63*90c8c64dSAndroid Build Coastguard Worker      this.traces.addTrace(trace);
64*90c8c64dSAndroid Build Coastguard Worker    });
65*90c8c64dSAndroid Build Coastguard Worker
66*90c8c64dSAndroid Build Coastguard Worker    const transitionTrace = this.traces.getTrace(TraceType.TRANSITION);
67*90c8c64dSAndroid Build Coastguard Worker    if (transitionTrace) {
68*90c8c64dSAndroid Build Coastguard Worker      let someCorrupted = false;
69*90c8c64dSAndroid Build Coastguard Worker      await Promise.all(
70*90c8c64dSAndroid Build Coastguard Worker        transitionTrace.mapEntry(async (entry) => {
71*90c8c64dSAndroid Build Coastguard Worker          let transition: PropertyTreeNode | undefined;
72*90c8c64dSAndroid Build Coastguard Worker          try {
73*90c8c64dSAndroid Build Coastguard Worker            transition = await entry.getValue();
74*90c8c64dSAndroid Build Coastguard Worker          } catch (e) {
75*90c8c64dSAndroid Build Coastguard Worker            someCorrupted = true;
76*90c8c64dSAndroid Build Coastguard Worker          }
77*90c8c64dSAndroid Build Coastguard Worker          this.transitionEntries.push(transition);
78*90c8c64dSAndroid Build Coastguard Worker        }),
79*90c8c64dSAndroid Build Coastguard Worker      );
80*90c8c64dSAndroid Build Coastguard Worker      if (someCorrupted) {
81*90c8c64dSAndroid Build Coastguard Worker        UserNotifier.add(new CannotParseAllTransitions());
82*90c8c64dSAndroid Build Coastguard Worker      }
83*90c8c64dSAndroid Build Coastguard Worker    }
84*90c8c64dSAndroid Build Coastguard Worker
85*90c8c64dSAndroid Build Coastguard Worker    this.screenRecordingVideo = screenRecordingVideo;
86*90c8c64dSAndroid Build Coastguard Worker    this.firstEntry = this.findFirstEntry();
87*90c8c64dSAndroid Build Coastguard Worker    this.lastEntry = this.findLastEntry();
88*90c8c64dSAndroid Build Coastguard Worker
89*90c8c64dSAndroid Build Coastguard Worker    const tracesSortedByDisplayOrder = traces
90*90c8c64dSAndroid Build Coastguard Worker      .mapTrace((trace) => trace)
91*90c8c64dSAndroid Build Coastguard Worker      .filter((trace) => TraceTypeUtils.isTraceTypeWithViewer(trace.type))
92*90c8c64dSAndroid Build Coastguard Worker      .sort((a, b) => {
93*90c8c64dSAndroid Build Coastguard Worker        // do not set screen recording as active unless it is the only trace
94*90c8c64dSAndroid Build Coastguard Worker        if (a.type === TraceType.SCREEN_RECORDING) return 1;
95*90c8c64dSAndroid Build Coastguard Worker        if (b.type === TraceType.SCREEN_RECORDING) return -1;
96*90c8c64dSAndroid Build Coastguard Worker        return TraceTypeUtils.compareByDisplayOrder(a.type, b.type);
97*90c8c64dSAndroid Build Coastguard Worker      });
98*90c8c64dSAndroid Build Coastguard Worker    if (tracesSortedByDisplayOrder.length > 0) {
99*90c8c64dSAndroid Build Coastguard Worker      this.trySetActiveTrace(tracesSortedByDisplayOrder[0]);
100*90c8c64dSAndroid Build Coastguard Worker    }
101*90c8c64dSAndroid Build Coastguard Worker  }
102*90c8c64dSAndroid Build Coastguard Worker
103*90c8c64dSAndroid Build Coastguard Worker  getTransitionEntries(): Array<PropertyTreeNode | undefined> {
104*90c8c64dSAndroid Build Coastguard Worker    return this.transitionEntries;
105*90c8c64dSAndroid Build Coastguard Worker  }
106*90c8c64dSAndroid Build Coastguard Worker
107*90c8c64dSAndroid Build Coastguard Worker  getTimestampConverter(): ComponentTimestampConverter | undefined {
108*90c8c64dSAndroid Build Coastguard Worker    return this.timestampConverter;
109*90c8c64dSAndroid Build Coastguard Worker  }
110*90c8c64dSAndroid Build Coastguard Worker
111*90c8c64dSAndroid Build Coastguard Worker  getCurrentPosition(): TracePosition | undefined {
112*90c8c64dSAndroid Build Coastguard Worker    if (this.explicitlySetPosition) {
113*90c8c64dSAndroid Build Coastguard Worker      return this.explicitlySetPosition;
114*90c8c64dSAndroid Build Coastguard Worker    }
115*90c8c64dSAndroid Build Coastguard Worker
116*90c8c64dSAndroid Build Coastguard Worker    let currentPosition: TracePosition | undefined = undefined;
117*90c8c64dSAndroid Build Coastguard Worker    if (this.firstEntry) {
118*90c8c64dSAndroid Build Coastguard Worker      currentPosition = TracePosition.fromTraceEntry(this.firstEntry);
119*90c8c64dSAndroid Build Coastguard Worker    }
120*90c8c64dSAndroid Build Coastguard Worker
121*90c8c64dSAndroid Build Coastguard Worker    const firstActiveEntry = this.getFirstEntryOfActiveViewTrace();
122*90c8c64dSAndroid Build Coastguard Worker    if (firstActiveEntry) {
123*90c8c64dSAndroid Build Coastguard Worker      currentPosition = TracePosition.fromTraceEntry(firstActiveEntry);
124*90c8c64dSAndroid Build Coastguard Worker    }
125*90c8c64dSAndroid Build Coastguard Worker
126*90c8c64dSAndroid Build Coastguard Worker    if (
127*90c8c64dSAndroid Build Coastguard Worker      this.lastReturnedCurrentPosition === undefined ||
128*90c8c64dSAndroid Build Coastguard Worker      currentPosition === undefined ||
129*90c8c64dSAndroid Build Coastguard Worker      !this.lastReturnedCurrentPosition.isEqual(currentPosition)
130*90c8c64dSAndroid Build Coastguard Worker    ) {
131*90c8c64dSAndroid Build Coastguard Worker      this.lastReturnedCurrentPosition = currentPosition;
132*90c8c64dSAndroid Build Coastguard Worker    }
133*90c8c64dSAndroid Build Coastguard Worker
134*90c8c64dSAndroid Build Coastguard Worker    return this.lastReturnedCurrentPosition;
135*90c8c64dSAndroid Build Coastguard Worker  }
136*90c8c64dSAndroid Build Coastguard Worker
137*90c8c64dSAndroid Build Coastguard Worker  setPosition(position: TracePosition | undefined) {
138*90c8c64dSAndroid Build Coastguard Worker    if (!this.hasTimestamps()) {
139*90c8c64dSAndroid Build Coastguard Worker      console.warn(
140*90c8c64dSAndroid Build Coastguard Worker        'Attempted to set position on traces with no timestamps/entries...',
141*90c8c64dSAndroid Build Coastguard Worker      );
142*90c8c64dSAndroid Build Coastguard Worker      return;
143*90c8c64dSAndroid Build Coastguard Worker    }
144*90c8c64dSAndroid Build Coastguard Worker
145*90c8c64dSAndroid Build Coastguard Worker    if (this.firstEntry && position) {
146*90c8c64dSAndroid Build Coastguard Worker      if (
147*90c8c64dSAndroid Build Coastguard Worker        this.firstEntry.getTimestamp().getValueNs() >
148*90c8c64dSAndroid Build Coastguard Worker        position.timestamp.getValueNs()
149*90c8c64dSAndroid Build Coastguard Worker      ) {
150*90c8c64dSAndroid Build Coastguard Worker        this.explicitlySetPosition = TracePosition.fromTraceEntry(
151*90c8c64dSAndroid Build Coastguard Worker          this.firstEntry,
152*90c8c64dSAndroid Build Coastguard Worker        );
153*90c8c64dSAndroid Build Coastguard Worker        return;
154*90c8c64dSAndroid Build Coastguard Worker      }
155*90c8c64dSAndroid Build Coastguard Worker    }
156*90c8c64dSAndroid Build Coastguard Worker
157*90c8c64dSAndroid Build Coastguard Worker    if (this.lastEntry && position) {
158*90c8c64dSAndroid Build Coastguard Worker      if (
159*90c8c64dSAndroid Build Coastguard Worker        this.lastEntry.getTimestamp().getValueNs() <
160*90c8c64dSAndroid Build Coastguard Worker        position.timestamp.getValueNs()
161*90c8c64dSAndroid Build Coastguard Worker      ) {
162*90c8c64dSAndroid Build Coastguard Worker        this.explicitlySetPosition = TracePosition.fromTraceEntry(
163*90c8c64dSAndroid Build Coastguard Worker          this.lastEntry,
164*90c8c64dSAndroid Build Coastguard Worker        );
165*90c8c64dSAndroid Build Coastguard Worker        return;
166*90c8c64dSAndroid Build Coastguard Worker      }
167*90c8c64dSAndroid Build Coastguard Worker    }
168*90c8c64dSAndroid Build Coastguard Worker
169*90c8c64dSAndroid Build Coastguard Worker    this.explicitlySetPosition = position;
170*90c8c64dSAndroid Build Coastguard Worker  }
171*90c8c64dSAndroid Build Coastguard Worker
172*90c8c64dSAndroid Build Coastguard Worker  makePositionFromActiveTrace(timestamp: Timestamp): TracePosition {
173*90c8c64dSAndroid Build Coastguard Worker    if (!this.activeTrace) {
174*90c8c64dSAndroid Build Coastguard Worker      return TracePosition.fromTimestamp(timestamp);
175*90c8c64dSAndroid Build Coastguard Worker    }
176*90c8c64dSAndroid Build Coastguard Worker
177*90c8c64dSAndroid Build Coastguard Worker    const entry = this.activeTrace.findClosestEntry(timestamp);
178*90c8c64dSAndroid Build Coastguard Worker    if (!entry) {
179*90c8c64dSAndroid Build Coastguard Worker      return TracePosition.fromTimestamp(timestamp);
180*90c8c64dSAndroid Build Coastguard Worker    }
181*90c8c64dSAndroid Build Coastguard Worker
182*90c8c64dSAndroid Build Coastguard Worker    return TracePosition.fromTraceEntry(entry, timestamp);
183*90c8c64dSAndroid Build Coastguard Worker  }
184*90c8c64dSAndroid Build Coastguard Worker
185*90c8c64dSAndroid Build Coastguard Worker  trySetActiveTrace(trace: Trace<object>): boolean {
186*90c8c64dSAndroid Build Coastguard Worker    const isTraceWithValidTimestamps = this.traces.hasTrace(trace);
187*90c8c64dSAndroid Build Coastguard Worker    if (this.activeTrace !== trace && isTraceWithValidTimestamps) {
188*90c8c64dSAndroid Build Coastguard Worker      this.activeTrace = trace;
189*90c8c64dSAndroid Build Coastguard Worker      return true;
190*90c8c64dSAndroid Build Coastguard Worker    }
191*90c8c64dSAndroid Build Coastguard Worker    return false;
192*90c8c64dSAndroid Build Coastguard Worker  }
193*90c8c64dSAndroid Build Coastguard Worker
194*90c8c64dSAndroid Build Coastguard Worker  getActiveTrace() {
195*90c8c64dSAndroid Build Coastguard Worker    return this.activeTrace;
196*90c8c64dSAndroid Build Coastguard Worker  }
197*90c8c64dSAndroid Build Coastguard Worker
198*90c8c64dSAndroid Build Coastguard Worker  getFullTimeRange(): TimeRange {
199*90c8c64dSAndroid Build Coastguard Worker    if (!this.firstEntry || !this.lastEntry) {
200*90c8c64dSAndroid Build Coastguard Worker      throw new Error(
201*90c8c64dSAndroid Build Coastguard Worker        'Trying to get full time range when there are no timestamps',
202*90c8c64dSAndroid Build Coastguard Worker      );
203*90c8c64dSAndroid Build Coastguard Worker    }
204*90c8c64dSAndroid Build Coastguard Worker
205*90c8c64dSAndroid Build Coastguard Worker    const fullTimeRange = new TimeRange(
206*90c8c64dSAndroid Build Coastguard Worker      this.firstEntry.getTimestamp(),
207*90c8c64dSAndroid Build Coastguard Worker      this.lastEntry.getTimestamp(),
208*90c8c64dSAndroid Build Coastguard Worker    );
209*90c8c64dSAndroid Build Coastguard Worker
210*90c8c64dSAndroid Build Coastguard Worker    if (
211*90c8c64dSAndroid Build Coastguard Worker      this.lastReturnedFullTimeRange === undefined ||
212*90c8c64dSAndroid Build Coastguard Worker      this.lastReturnedFullTimeRange.from.getValueNs() !==
213*90c8c64dSAndroid Build Coastguard Worker        fullTimeRange.from.getValueNs() ||
214*90c8c64dSAndroid Build Coastguard Worker      this.lastReturnedFullTimeRange.to.getValueNs() !==
215*90c8c64dSAndroid Build Coastguard Worker        fullTimeRange.to.getValueNs()
216*90c8c64dSAndroid Build Coastguard Worker    ) {
217*90c8c64dSAndroid Build Coastguard Worker      this.lastReturnedFullTimeRange = fullTimeRange;
218*90c8c64dSAndroid Build Coastguard Worker    }
219*90c8c64dSAndroid Build Coastguard Worker
220*90c8c64dSAndroid Build Coastguard Worker    return this.lastReturnedFullTimeRange;
221*90c8c64dSAndroid Build Coastguard Worker  }
222*90c8c64dSAndroid Build Coastguard Worker
223*90c8c64dSAndroid Build Coastguard Worker  getSelectionTimeRange(): TimeRange {
224*90c8c64dSAndroid Build Coastguard Worker    if (this.explicitlySetSelection === undefined) {
225*90c8c64dSAndroid Build Coastguard Worker      return this.getFullTimeRange();
226*90c8c64dSAndroid Build Coastguard Worker    } else {
227*90c8c64dSAndroid Build Coastguard Worker      return this.explicitlySetSelection;
228*90c8c64dSAndroid Build Coastguard Worker    }
229*90c8c64dSAndroid Build Coastguard Worker  }
230*90c8c64dSAndroid Build Coastguard Worker
231*90c8c64dSAndroid Build Coastguard Worker  setSelectionTimeRange(selection: TimeRange) {
232*90c8c64dSAndroid Build Coastguard Worker    this.explicitlySetSelection = selection;
233*90c8c64dSAndroid Build Coastguard Worker  }
234*90c8c64dSAndroid Build Coastguard Worker
235*90c8c64dSAndroid Build Coastguard Worker  getZoomRange(): TimeRange {
236*90c8c64dSAndroid Build Coastguard Worker    if (this.explicitlySetZoomRange === undefined) {
237*90c8c64dSAndroid Build Coastguard Worker      return this.getFullTimeRange();
238*90c8c64dSAndroid Build Coastguard Worker    } else {
239*90c8c64dSAndroid Build Coastguard Worker      return this.explicitlySetZoomRange;
240*90c8c64dSAndroid Build Coastguard Worker    }
241*90c8c64dSAndroid Build Coastguard Worker  }
242*90c8c64dSAndroid Build Coastguard Worker
243*90c8c64dSAndroid Build Coastguard Worker  setZoom(zoomRange: TimeRange) {
244*90c8c64dSAndroid Build Coastguard Worker    this.explicitlySetZoomRange = zoomRange;
245*90c8c64dSAndroid Build Coastguard Worker  }
246*90c8c64dSAndroid Build Coastguard Worker
247*90c8c64dSAndroid Build Coastguard Worker  getTraces(): Traces {
248*90c8c64dSAndroid Build Coastguard Worker    return this.traces;
249*90c8c64dSAndroid Build Coastguard Worker  }
250*90c8c64dSAndroid Build Coastguard Worker
251*90c8c64dSAndroid Build Coastguard Worker  hasTrace(trace: Trace<object>): boolean {
252*90c8c64dSAndroid Build Coastguard Worker    return this.traces.hasTrace(trace);
253*90c8c64dSAndroid Build Coastguard Worker  }
254*90c8c64dSAndroid Build Coastguard Worker
255*90c8c64dSAndroid Build Coastguard Worker  getScreenRecordingVideo(): Blob | undefined {
256*90c8c64dSAndroid Build Coastguard Worker    return this.screenRecordingVideo;
257*90c8c64dSAndroid Build Coastguard Worker  }
258*90c8c64dSAndroid Build Coastguard Worker
259*90c8c64dSAndroid Build Coastguard Worker  searchCorrespondingScreenRecordingTimeSeconds(
260*90c8c64dSAndroid Build Coastguard Worker    position: TracePosition,
261*90c8c64dSAndroid Build Coastguard Worker  ): number | undefined {
262*90c8c64dSAndroid Build Coastguard Worker    const trace = this.traces.getTrace(TraceType.SCREEN_RECORDING);
263*90c8c64dSAndroid Build Coastguard Worker    if (!trace) {
264*90c8c64dSAndroid Build Coastguard Worker      return undefined;
265*90c8c64dSAndroid Build Coastguard Worker    }
266*90c8c64dSAndroid Build Coastguard Worker
267*90c8c64dSAndroid Build Coastguard Worker    const firstTimestamp = trace.getEntry(0).getTimestamp();
268*90c8c64dSAndroid Build Coastguard Worker    const entry = TraceEntryFinder.findCorrespondingEntry(trace, position);
269*90c8c64dSAndroid Build Coastguard Worker    if (!entry) {
270*90c8c64dSAndroid Build Coastguard Worker      return undefined;
271*90c8c64dSAndroid Build Coastguard Worker    }
272*90c8c64dSAndroid Build Coastguard Worker
273*90c8c64dSAndroid Build Coastguard Worker    return ScreenRecordingUtils.timestampToVideoTimeSeconds(
274*90c8c64dSAndroid Build Coastguard Worker      firstTimestamp.getValueNs(),
275*90c8c64dSAndroid Build Coastguard Worker      entry.getTimestamp().getValueNs(),
276*90c8c64dSAndroid Build Coastguard Worker    );
277*90c8c64dSAndroid Build Coastguard Worker  }
278*90c8c64dSAndroid Build Coastguard Worker
279*90c8c64dSAndroid Build Coastguard Worker  hasTimestamps(): boolean {
280*90c8c64dSAndroid Build Coastguard Worker    return this.firstEntry !== undefined;
281*90c8c64dSAndroid Build Coastguard Worker  }
282*90c8c64dSAndroid Build Coastguard Worker
283*90c8c64dSAndroid Build Coastguard Worker  hasMoreThanOneDistinctTimestamp(): boolean {
284*90c8c64dSAndroid Build Coastguard Worker    return (
285*90c8c64dSAndroid Build Coastguard Worker      this.hasTimestamps() &&
286*90c8c64dSAndroid Build Coastguard Worker      this.firstEntry?.getTimestamp().getValueNs() !==
287*90c8c64dSAndroid Build Coastguard Worker        this.lastEntry?.getTimestamp().getValueNs()
288*90c8c64dSAndroid Build Coastguard Worker    );
289*90c8c64dSAndroid Build Coastguard Worker  }
290*90c8c64dSAndroid Build Coastguard Worker
291*90c8c64dSAndroid Build Coastguard Worker  getPreviousEntryFor(trace: Trace<object>): TraceEntry<object> | undefined {
292*90c8c64dSAndroid Build Coastguard Worker    if (trace.lengthEntries === 0) {
293*90c8c64dSAndroid Build Coastguard Worker      return undefined;
294*90c8c64dSAndroid Build Coastguard Worker    }
295*90c8c64dSAndroid Build Coastguard Worker
296*90c8c64dSAndroid Build Coastguard Worker    const currentIndex = this.findCurrentEntryFor(trace)?.getIndex();
297*90c8c64dSAndroid Build Coastguard Worker    if (currentIndex === undefined || currentIndex === 0) {
298*90c8c64dSAndroid Build Coastguard Worker      return undefined;
299*90c8c64dSAndroid Build Coastguard Worker    }
300*90c8c64dSAndroid Build Coastguard Worker
301*90c8c64dSAndroid Build Coastguard Worker    return trace.getEntry(currentIndex - 1);
302*90c8c64dSAndroid Build Coastguard Worker  }
303*90c8c64dSAndroid Build Coastguard Worker
304*90c8c64dSAndroid Build Coastguard Worker  getNextEntryFor(trace: Trace<object>): TraceEntry<object> | undefined {
305*90c8c64dSAndroid Build Coastguard Worker    if (trace.lengthEntries === 0) {
306*90c8c64dSAndroid Build Coastguard Worker      return undefined;
307*90c8c64dSAndroid Build Coastguard Worker    }
308*90c8c64dSAndroid Build Coastguard Worker
309*90c8c64dSAndroid Build Coastguard Worker    const currentIndex = this.findCurrentEntryFor(trace)?.getIndex();
310*90c8c64dSAndroid Build Coastguard Worker    if (currentIndex === undefined) {
311*90c8c64dSAndroid Build Coastguard Worker      return trace.getEntry(0);
312*90c8c64dSAndroid Build Coastguard Worker    }
313*90c8c64dSAndroid Build Coastguard Worker
314*90c8c64dSAndroid Build Coastguard Worker    if (currentIndex + 1 >= trace.lengthEntries) {
315*90c8c64dSAndroid Build Coastguard Worker      return undefined;
316*90c8c64dSAndroid Build Coastguard Worker    }
317*90c8c64dSAndroid Build Coastguard Worker
318*90c8c64dSAndroid Build Coastguard Worker    return trace.getEntry(currentIndex + 1);
319*90c8c64dSAndroid Build Coastguard Worker  }
320*90c8c64dSAndroid Build Coastguard Worker
321*90c8c64dSAndroid Build Coastguard Worker  findCurrentEntryFor(trace: Trace<object>): TraceEntry<object> | undefined {
322*90c8c64dSAndroid Build Coastguard Worker    const position = this.getCurrentPosition();
323*90c8c64dSAndroid Build Coastguard Worker    if (!position) {
324*90c8c64dSAndroid Build Coastguard Worker      return undefined;
325*90c8c64dSAndroid Build Coastguard Worker    }
326*90c8c64dSAndroid Build Coastguard Worker
327*90c8c64dSAndroid Build Coastguard Worker    const entry = TraceEntryFinder.findCorrespondingEntry(trace, position);
328*90c8c64dSAndroid Build Coastguard Worker
329*90c8c64dSAndroid Build Coastguard Worker    if (
330*90c8c64dSAndroid Build Coastguard Worker      this.lastReturnedCurrentEntries.get(trace)?.getIndex() !==
331*90c8c64dSAndroid Build Coastguard Worker      entry?.getIndex()
332*90c8c64dSAndroid Build Coastguard Worker    ) {
333*90c8c64dSAndroid Build Coastguard Worker      this.lastReturnedCurrentEntries.set(trace, entry);
334*90c8c64dSAndroid Build Coastguard Worker    }
335*90c8c64dSAndroid Build Coastguard Worker
336*90c8c64dSAndroid Build Coastguard Worker    return this.lastReturnedCurrentEntries.get(trace);
337*90c8c64dSAndroid Build Coastguard Worker  }
338*90c8c64dSAndroid Build Coastguard Worker
339*90c8c64dSAndroid Build Coastguard Worker  moveToPreviousEntryFor(trace: Trace<object>) {
340*90c8c64dSAndroid Build Coastguard Worker    const prevEntry = this.getPreviousEntryFor(trace);
341*90c8c64dSAndroid Build Coastguard Worker    if (prevEntry !== undefined) {
342*90c8c64dSAndroid Build Coastguard Worker      this.setPosition(TracePosition.fromTraceEntry(prevEntry));
343*90c8c64dSAndroid Build Coastguard Worker    }
344*90c8c64dSAndroid Build Coastguard Worker  }
345*90c8c64dSAndroid Build Coastguard Worker
346*90c8c64dSAndroid Build Coastguard Worker  moveToNextEntryFor(trace: Trace<object>) {
347*90c8c64dSAndroid Build Coastguard Worker    const nextEntry = this.getNextEntryFor(trace);
348*90c8c64dSAndroid Build Coastguard Worker    if (nextEntry !== undefined) {
349*90c8c64dSAndroid Build Coastguard Worker      this.setPosition(TracePosition.fromTraceEntry(nextEntry));
350*90c8c64dSAndroid Build Coastguard Worker    }
351*90c8c64dSAndroid Build Coastguard Worker  }
352*90c8c64dSAndroid Build Coastguard Worker
353*90c8c64dSAndroid Build Coastguard Worker  clear() {
354*90c8c64dSAndroid Build Coastguard Worker    this.traces = new Traces();
355*90c8c64dSAndroid Build Coastguard Worker    this.firstEntry = undefined;
356*90c8c64dSAndroid Build Coastguard Worker    this.lastEntry = undefined;
357*90c8c64dSAndroid Build Coastguard Worker    this.explicitlySetPosition = undefined;
358*90c8c64dSAndroid Build Coastguard Worker    this.explicitlySetSelection = undefined;
359*90c8c64dSAndroid Build Coastguard Worker    this.lastReturnedCurrentPosition = undefined;
360*90c8c64dSAndroid Build Coastguard Worker    this.screenRecordingVideo = undefined;
361*90c8c64dSAndroid Build Coastguard Worker    this.lastReturnedFullTimeRange = undefined;
362*90c8c64dSAndroid Build Coastguard Worker    this.lastReturnedCurrentEntries.clear();
363*90c8c64dSAndroid Build Coastguard Worker    this.activeTrace = undefined;
364*90c8c64dSAndroid Build Coastguard Worker  }
365*90c8c64dSAndroid Build Coastguard Worker
366*90c8c64dSAndroid Build Coastguard Worker  private findFirstEntry(): TraceEntry<{}> | undefined {
367*90c8c64dSAndroid Build Coastguard Worker    let first: TraceEntry<{}> | undefined;
368*90c8c64dSAndroid Build Coastguard Worker
369*90c8c64dSAndroid Build Coastguard Worker    this.traces.forEachTrace((trace) => {
370*90c8c64dSAndroid Build Coastguard Worker      let candidate: TraceEntry<{}> | undefined;
371*90c8c64dSAndroid Build Coastguard Worker      for (let i = 0; i < trace.lengthEntries; i++) {
372*90c8c64dSAndroid Build Coastguard Worker        const entry = trace.getEntry(i);
373*90c8c64dSAndroid Build Coastguard Worker        if (entry.hasValidTimestamp()) {
374*90c8c64dSAndroid Build Coastguard Worker          candidate = entry;
375*90c8c64dSAndroid Build Coastguard Worker          break;
376*90c8c64dSAndroid Build Coastguard Worker        }
377*90c8c64dSAndroid Build Coastguard Worker      }
378*90c8c64dSAndroid Build Coastguard Worker      if (
379*90c8c64dSAndroid Build Coastguard Worker        candidate &&
380*90c8c64dSAndroid Build Coastguard Worker        (!first || candidate.getTimestamp() < first.getTimestamp())
381*90c8c64dSAndroid Build Coastguard Worker      ) {
382*90c8c64dSAndroid Build Coastguard Worker        first = candidate;
383*90c8c64dSAndroid Build Coastguard Worker      }
384*90c8c64dSAndroid Build Coastguard Worker    });
385*90c8c64dSAndroid Build Coastguard Worker
386*90c8c64dSAndroid Build Coastguard Worker    return first;
387*90c8c64dSAndroid Build Coastguard Worker  }
388*90c8c64dSAndroid Build Coastguard Worker
389*90c8c64dSAndroid Build Coastguard Worker  private findLastEntry(): TraceEntry<{}> | undefined {
390*90c8c64dSAndroid Build Coastguard Worker    let last: TraceEntry<{}> | undefined = undefined;
391*90c8c64dSAndroid Build Coastguard Worker
392*90c8c64dSAndroid Build Coastguard Worker    this.traces.forEachTrace((trace) => {
393*90c8c64dSAndroid Build Coastguard Worker      const candidate = trace.getEntry(trace.lengthEntries - 1);
394*90c8c64dSAndroid Build Coastguard Worker      if (!last || candidate.getTimestamp() > last.getTimestamp()) {
395*90c8c64dSAndroid Build Coastguard Worker        last = candidate;
396*90c8c64dSAndroid Build Coastguard Worker      }
397*90c8c64dSAndroid Build Coastguard Worker    });
398*90c8c64dSAndroid Build Coastguard Worker
399*90c8c64dSAndroid Build Coastguard Worker    return last;
400*90c8c64dSAndroid Build Coastguard Worker  }
401*90c8c64dSAndroid Build Coastguard Worker
402*90c8c64dSAndroid Build Coastguard Worker  private getFirstEntryOfActiveViewTrace(): TraceEntry<{}> | undefined {
403*90c8c64dSAndroid Build Coastguard Worker    if (!this.activeTrace) {
404*90c8c64dSAndroid Build Coastguard Worker      return undefined;
405*90c8c64dSAndroid Build Coastguard Worker    }
406*90c8c64dSAndroid Build Coastguard Worker    return this.activeTrace.getEntry(0);
407*90c8c64dSAndroid Build Coastguard Worker  }
408*90c8c64dSAndroid Build Coastguard Worker}
409