blob: 5513b90bd2de067ab1ade821c9b5d29c0341b42a [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {BuganizerAttachmentsDownloadEmitter} from 'interfaces/buganizer_attachments_download_emitter';
import {FilesDownloadListener} from 'interfaces/files_download_listener';
import {RemoteBugreportReceiver} from 'interfaces/remote_bugreport_receiver';
import {RemoteTimestampReceiver} from 'interfaces/remote_timestamp_receiver';
import {RemoteTimestampSender} from 'interfaces/remote_timestamp_sender';
import {Runnable} from 'interfaces/runnable';
import {TraceDataListener} from 'interfaces/trace_data_listener';
import {TracePositionUpdateListener} from 'interfaces/trace_position_update_listener';
import {Timestamp, TimestampType} from 'trace/timestamp';
import {TracePosition} from 'trace/trace_position';
import {TraceType} from 'trace/trace_type';
import {Viewer} from 'viewers/viewer';
import {ViewerFactory} from 'viewers/viewer_factory';
import {TimelineData} from './timeline_data';
import {TracePipeline} from './trace_pipeline';
export type CrossToolProtocolDependencyInversion = RemoteBugreportReceiver &
RemoteTimestampReceiver &
RemoteTimestampSender;
export type AbtChromeExtensionProtocolDependencyInversion = BuganizerAttachmentsDownloadEmitter &
Runnable;
export type AppComponentDependencyInversion = TraceDataListener;
export type TimelineComponentDependencyInversion = TracePositionUpdateListener;
export type UploadTracesComponentDependencyInversion = FilesDownloadListener;
export class Mediator {
private abtChromeExtensionProtocol: AbtChromeExtensionProtocolDependencyInversion;
private crossToolProtocol: CrossToolProtocolDependencyInversion;
private uploadTracesComponent?: UploadTracesComponentDependencyInversion;
private timelineComponent?: TimelineComponentDependencyInversion;
private appComponent: AppComponentDependencyInversion;
private storage: Storage;
private tracePipeline: TracePipeline;
private timelineData: TimelineData;
private viewers: Viewer[] = [];
private isChangingCurrentTimestamp = false;
private isTraceDataVisualized = false;
private lastRemoteToolTimestampReceived: Timestamp | undefined;
constructor(
tracePipeline: TracePipeline,
timelineData: TimelineData,
abtChromeExtensionProtocol: AbtChromeExtensionProtocolDependencyInversion,
crossToolProtocol: CrossToolProtocolDependencyInversion,
appComponent: AppComponentDependencyInversion,
storage: Storage
) {
this.tracePipeline = tracePipeline;
this.timelineData = timelineData;
this.abtChromeExtensionProtocol = abtChromeExtensionProtocol;
this.crossToolProtocol = crossToolProtocol;
this.appComponent = appComponent;
this.storage = storage;
this.timelineData.setOnTracePositionUpdate((position) => {
this.onWinscopeTracePositionUpdate(position);
});
this.crossToolProtocol.setOnBugreportReceived(
async (bugreport: File, timestamp?: Timestamp) => {
await this.onRemoteBugreportReceived(bugreport, timestamp);
}
);
this.crossToolProtocol.setOnTimestampReceived(async (timestamp: Timestamp) => {
this.onRemoteTimestampReceived(timestamp);
});
this.abtChromeExtensionProtocol.setOnBuganizerAttachmentsDownloadStart(() => {
this.onBuganizerAttachmentsDownloadStart();
});
this.abtChromeExtensionProtocol.setOnBuganizerAttachmentsDownloaded(
async (attachments: File[]) => {
await this.onBuganizerAttachmentsDownloaded(attachments);
}
);
}
setUploadTracesComponent(
uploadTracesComponent: UploadTracesComponentDependencyInversion | undefined
) {
this.uploadTracesComponent = uploadTracesComponent;
}
setTimelineComponent(timelineComponent: TimelineComponentDependencyInversion | undefined) {
this.timelineComponent = timelineComponent;
}
onWinscopeInitialized() {
this.abtChromeExtensionProtocol.run();
}
onWinscopeUploadNew() {
this.resetAppToInitialState();
}
onWinscopeTraceDataLoaded() {
this.processTraces();
}
onWinscopeTracePositionUpdate(position: TracePosition) {
this.executeIgnoringRecursiveTimestampNotifications(() => {
this.updateViewersTracePosition(position);
const timestamp = position.timestamp;
if (timestamp.getType() !== TimestampType.REAL) {
console.warn(
'Cannot propagate timestamp change to remote tool.' +
` Remote tool expects timestamp type ${TimestampType.REAL},` +
` but Winscope wants to notify timestamp type ${timestamp.getType()}.`
);
} else {
this.crossToolProtocol.sendTimestamp(timestamp);
}
this.timelineComponent?.onTracePositionUpdate(position);
});
}
private onBuganizerAttachmentsDownloadStart() {
this.resetAppToInitialState();
this.uploadTracesComponent?.onFilesDownloadStart();
}
private async onBuganizerAttachmentsDownloaded(attachments: File[]) {
await this.processRemoteFilesReceived(attachments);
}
private async onRemoteBugreportReceived(bugreport: File, timestamp?: Timestamp) {
await this.processRemoteFilesReceived([bugreport]);
if (timestamp !== undefined) {
this.onRemoteTimestampReceived(timestamp);
}
}
private onRemoteTimestampReceived(timestamp: Timestamp) {
this.executeIgnoringRecursiveTimestampNotifications(() => {
this.lastRemoteToolTimestampReceived = timestamp;
if (!this.isTraceDataVisualized) {
return; // apply timestamp later when traces are visualized
}
if (this.timelineData.getTimestampType() !== timestamp.getType()) {
console.warn(
'Cannot apply new timestamp received from remote tool.' +
` Remote tool notified timestamp type ${timestamp.getType()},` +
` but Winscope is accepting timestamp type ${this.timelineData.getTimestampType()}.`
);
return;
}
if (
this.timelineData.getCurrentPosition()?.timestamp.getValueNs() === timestamp.getValueNs()
) {
return; // no timestamp change
}
const position = TracePosition.fromTimestamp(timestamp);
this.updateViewersTracePosition(position);
this.timelineData.setPosition(position);
this.timelineComponent?.onTracePositionUpdate(position); //TODO: is this redundant?
});
}
private async processRemoteFilesReceived(files: File[]) {
this.resetAppToInitialState();
this.uploadTracesComponent?.onFilesDownloaded(files);
}
private processTraces() {
this.tracePipeline.buildTraces();
this.timelineData.initialize(
this.tracePipeline.getTraces(),
this.tracePipeline.getScreenRecordingVideo()
);
this.createViewers();
this.appComponent.onTraceDataLoaded(this.viewers);
this.isTraceDataVisualized = true;
if (this.lastRemoteToolTimestampReceived !== undefined) {
this.onRemoteTimestampReceived(this.lastRemoteToolTimestampReceived);
}
}
private createViewers() {
const traces = this.tracePipeline.getTraces();
const traceTypes = new Set<TraceType>();
traces.forEachTrace((trace) => {
traceTypes.add(trace.type);
});
this.viewers = new ViewerFactory().createViewers(traceTypes, traces, this.storage);
// Update the viewers as soon as they are created
const position = this.timelineData.getCurrentPosition();
if (position) {
this.onWinscopeTracePositionUpdate(position);
}
}
private updateViewersTracePosition(position: TracePosition) {
this.viewers.forEach((viewer) => {
viewer.onTracePositionUpdate(position);
});
}
private executeIgnoringRecursiveTimestampNotifications(op: () => void) {
if (this.isChangingCurrentTimestamp) {
return;
}
this.isChangingCurrentTimestamp = true;
try {
op();
} finally {
this.isChangingCurrentTimestamp = false;
}
}
private resetAppToInitialState() {
this.tracePipeline.clear();
this.timelineData.clear();
this.viewers = [];
this.isTraceDataVisualized = false;
this.lastRemoteToolTimestampReceived = undefined;
this.appComponent.onTraceDataUnloaded();
}
}