Merge changes Iaf248668,Ic1701cd0,I56274973

* changes:
  Mediator cleanup
  Refactor dependency inversion interfaces
  Remove legacy data and properties view from layer trace root
This commit is contained in:
Kean Mariotti
2022-12-28 18:59:19 +00:00
committed by Android (Google) Code Review
30 changed files with 306 additions and 199 deletions

View File

@@ -14,29 +14,29 @@
* limitations under the License.
*/
import {
AbtChromeExtensionProtocolDependencyInversion,
OnBugAttachmentsDownloadStart,
OnBugAttachmentsReceived
} from "./abt_chrome_extension_protocol_dependency_inversion";
import {
MessageType,
OpenBuganizerResponse,
OpenRequest,
WebCommandMessage} from "./messages";
import {FunctionUtils} from "common/utils/function_utils";
import {
BuganizerAttachmentsDownloadEmitter,
OnBuganizerAttachmentsDownloadStart,
OnBuganizerAttachmentsDownloaded
} from "interfaces/buganizer_attachments_download_emitter";
export class AbtChromeExtensionProtocol implements AbtChromeExtensionProtocolDependencyInversion {
export class AbtChromeExtensionProtocol implements BuganizerAttachmentsDownloadEmitter {
static readonly ABT_EXTENSION_ID = "mbbaofdfoekifkfpgehgffcpagbbjkmj";
private onBugAttachmentsDownloadStart: OnBugAttachmentsDownloadStart = FunctionUtils.DO_NOTHING;
private onBugAttachmentsReceived: OnBugAttachmentsReceived = FunctionUtils.DO_NOTHING_ASYNC;
private onAttachmentsDownloadStart: OnBuganizerAttachmentsDownloadStart = FunctionUtils.DO_NOTHING;
private onttachmentsDownloaded: OnBuganizerAttachmentsDownloaded = FunctionUtils.DO_NOTHING_ASYNC;
setOnBugAttachmentsDownloadStart(callback: OnBugAttachmentsDownloadStart) {
this.onBugAttachmentsDownloadStart = callback;
setOnBuganizerAttachmentsDownloadStart(callback: OnBuganizerAttachmentsDownloadStart) {
this.onAttachmentsDownloadStart = callback;
}
setOnBugAttachmentsReceived(callback: OnBugAttachmentsReceived) {
this.onBugAttachmentsReceived = callback;
setOnBuganizerAttachmentsDownloaded(callback: OnBuganizerAttachmentsDownloaded) {
this.onttachmentsDownloaded = callback;
}
run() {
@@ -45,7 +45,7 @@ export class AbtChromeExtensionProtocol implements AbtChromeExtensionProtocolDep
return;
}
this.onBugAttachmentsDownloadStart();
this.onAttachmentsDownloadStart();
const openRequestMessage: OpenRequest = {
action: MessageType.OPEN_REQUEST
@@ -88,7 +88,7 @@ export class AbtChromeExtensionProtocol implements AbtChromeExtensionProtocolDep
});
const files = await Promise.all(filesBlobPromises);
await this.onBugAttachmentsReceived(files);
await this.onttachmentsDownloaded(files);
}
private isOpenBuganizerResponseMessage(message: WebCommandMessage):

View File

@@ -14,23 +14,26 @@
* limitations under the License.
*/
import {FunctionUtils} from "common/utils/function_utils";
import {
OnBugAttachmentsDownloadStart,
OnBugAttachmentsReceived,
AbtChromeExtensionProtocolDependencyInversion
} from "./abt_chrome_extension_protocol_dependency_inversion";
import {FunctionUtils} from "../common/utils/function_utils";
BuganizerAttachmentsDownloadEmitter,
OnBuganizerAttachmentsDownloadStart,
OnBuganizerAttachmentsDownloaded
} from "interfaces/buganizer_attachments_download_emitter";
import {Runnable} from "interfaces/runnable";
export class AbtChromeExtensionProtocolStub implements AbtChromeExtensionProtocolDependencyInversion {
onBugAttachmentsDownloadStart: OnBugAttachmentsDownloadStart = FunctionUtils.DO_NOTHING;
onBugAttachmentsReceived: OnBugAttachmentsReceived = FunctionUtils.DO_NOTHING_ASYNC;
export class AbtChromeExtensionProtocolStub implements
BuganizerAttachmentsDownloadEmitter,
Runnable {
onBuganizerAttachmentsDownloadStart: OnBuganizerAttachmentsDownloadStart = FunctionUtils.DO_NOTHING;
onBuganizerAttachmentsDownloaded: OnBuganizerAttachmentsDownloaded = FunctionUtils.DO_NOTHING_ASYNC;
setOnBugAttachmentsDownloadStart(callback: OnBugAttachmentsDownloadStart) {
this.onBugAttachmentsDownloadStart = callback;
setOnBuganizerAttachmentsDownloadStart(callback: OnBuganizerAttachmentsDownloadStart) {
this.onBuganizerAttachmentsDownloadStart = callback;
}
setOnBugAttachmentsReceived(callback: OnBugAttachmentsReceived) {
this.onBugAttachmentsReceived = callback;
setOnBuganizerAttachmentsDownloaded(callback: OnBuganizerAttachmentsDownloaded) {
this.onBuganizerAttachmentsDownloaded = callback;
}
run() {

View File

@@ -23,7 +23,6 @@ import {
ViewEncapsulation
} from "@angular/core";
import { createCustomElement } from "@angular/elements";
import { AppComponentDependencyInversion } from "./app_component_dependency_inversion";
import { TimelineComponent} from "./timeline/timeline.component";
import {AbtChromeExtensionProtocol} from "abt_chrome_extension/abt_chrome_extension_protocol";
import {CrossToolProtocol} from "cross_tool/cross_tool_protocol";
@@ -45,6 +44,7 @@ import { TimelineData } from "app/timeline_data";
import { TracingConfig } from "trace_collection/tracing_config";
import {TRACE_INFO} from "app/trace_info";
import {UploadTracesComponent} from "./upload_traces.component";
import {TraceDataListener} from "interfaces/trace_data_listener";
@Component({
selector: "app-root",
@@ -65,7 +65,7 @@ import {UploadTracesComponent} from "./upload_traces.component";
</div>
<button *ngIf="dataLoaded" color="primary" mat-stroked-button
(click)="onUploadNewClick()">
(click)="mediator.onWinscopeUploadNew()">
Upload New
</button>
@@ -198,7 +198,7 @@ import {UploadTracesComponent} from "./upload_traces.component";
],
encapsulation: ViewEncapsulation.None
})
export class AppComponent implements AppComponentDependencyInversion {
export class AppComponent implements TraceDataListener {
title = "winscope-ng";
changeDetectorRef: ChangeDetectorRef;
traceData = new TraceData();
@@ -285,19 +285,18 @@ export class AppComponent implements AppComponentDependencyInversion {
return this.timelineData.getScreenRecordingVideo();
}
public onUploadNewClick() {
this.dataLoaded = false;
this.mediator.onWinscopeUploadNew();
proxyClient.adbData = [];
this.changeDetectorRef.detectChanges();
}
public onTraceDataLoaded(viewers: Viewer[]) {
this.viewers = viewers;
this.dataLoaded = true;
this.changeDetectorRef.detectChanges();
}
public onTraceDataUnloaded() {
proxyClient.adbData = [];
this.dataLoaded = false;
this.changeDetectorRef.detectChanges();
}
public setDarkMode(enabled: boolean) {
document.body.classList.toggle("dark-mode", enabled);
this.store.add("dark-mode", `${enabled}`);

View File

@@ -14,15 +14,15 @@
* limitations under the License.
*/
import {AppComponentDependencyInversion} from "./app_component_dependency_inversion";
import {Viewer} from "viewers/viewer";
import {TraceDataListener} from "interfaces/trace_data_listener";
export class AppComponentStub implements AppComponentDependencyInversion {
export class AppComponentStub implements TraceDataListener {
onTraceDataLoaded(viewers: Viewer[]) {
// do nothing
}
onUploadNewClick() {
onTraceDataUnloaded() {
// do nothing
}
}

View File

@@ -30,11 +30,11 @@ import { FormControl, FormGroup, Validators} from "@angular/forms";
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
import { TraceType } from "common/trace/trace_type";
import { TRACE_INFO } from "app/trace_info";
import { TimelineComponentDependencyInversion } from "./timeline_component_dependency_inversion";
import { TimelineData } from "app/timeline_data";
import { MiniTimelineComponent } from "./mini_timeline.component";
import { ElapsedTimestamp, RealTimestamp, Timestamp, TimestampType } from "common/trace/timestamp";
import { TimeUtils } from "common/utils/time_utils";
import {TimestampChangeListener} from "interfaces/timestamp_change_listener";
@Component({
selector: "timeline",
@@ -271,7 +271,7 @@ import { TimeUtils } from "common/utils/time_utils";
}
`],
})
export class TimelineComponent implements TimelineComponentDependencyInversion {
export class TimelineComponent implements TimestampChangeListener {
public readonly TOGGLE_BUTTON_CLASS: string = "button-toggle-expansion";
public readonly MAX_SELECTED_TRACES = 3;

View File

@@ -14,10 +14,10 @@
* limitations under the License.
*/
import {TimelineComponentDependencyInversion} from "./timeline_component_dependency_inversion";
import {Timestamp} from "common/trace/timestamp";
import {TimestampChangeListener} from "interfaces/timestamp_change_listener";
export class TimelineComponentStub implements TimelineComponentDependencyInversion {
export class TimelineComponentStub implements TimestampChangeListener {
onCurrentTimestampChanged(timestamp: Timestamp|undefined) {
// do nothing
}

View File

@@ -23,12 +23,12 @@ import {
Output
} from "@angular/core";
import {MatSnackBar} from "@angular/material/snack-bar";
import {UploadTracesComponentDependencyInversion} from "./upload_traces_component_dependency_inversion";
import {TraceData} from "app/trace_data";
import {TRACE_INFO} from "app/trace_info";
import {Trace, TraceFile} from "common/trace/trace";
import {FileUtils, OnFile} from "common/utils/file_utils";
import {ParserErrorSnackBarComponent} from "./parser_error_snack_bar_component";
import {FilesDownloadListener} from "interfaces/files_download_listener";
@Component({
selector: "upload-traces",
@@ -54,7 +54,7 @@ import {ParserErrorSnackBarComponent} from "./parser_error_snack_bar_component";
/>
<load-progress *ngIf="isLoadingFiles"
[progressPercentage]="progresPercentage"
[progressPercentage]="progressPercentage"
[message]="progressMessage">
</load-progress>
@@ -174,11 +174,11 @@ import {ParserErrorSnackBarComponent} from "./parser_error_snack_bar_component";
`
]
})
export class UploadTracesComponent implements UploadTracesComponentDependencyInversion {
export class UploadTracesComponent implements FilesDownloadListener {
TRACE_INFO = TRACE_INFO;
isLoadingFiles = false;
progressMessage = "";
progresPercentage?: number;
progressPercentage?: number;
@Input() traceData!: TraceData;
@Output() traceDataLoaded = new EventEmitter<void>();
@@ -197,52 +197,19 @@ export class UploadTracesComponent implements UploadTracesComponentDependencyInv
public onFilesDownloadStart() {
this.isLoadingFiles = true;
this.progressMessage = "Downloading files...";
this.progresPercentage = undefined;
this.progressPercentage = undefined;
this.changeDetectorRef.detectChanges();
}
public async onFilesDownloaded(files: File[]) {
await this.processFiles(files);
}
public async onInputFiles(event: Event) {
const files = this.getInputFiles(event);
await this.processFiles(files);
}
public async processFiles(files: File[]) {
const UI_PROGRESS_UPDATE_PERIOD_MS = 200;
let lastUiProgressUpdate = Date.now();
const onProgressUpdate = (progress: number) => {
const now = Date.now();
if ((Date.now() - lastUiProgressUpdate) < UI_PROGRESS_UPDATE_PERIOD_MS) {
// Let's limit the amount of UI updates, because the progress bar component
// renders weird stuff when updated too frequently
return;
}
lastUiProgressUpdate = now;
this.progresPercentage = progress;
this.changeDetectorRef.detectChanges();
};
const traceFiles: TraceFile[] = [];
const onFile: OnFile = (file: File, parentArchive?: File) => {
traceFiles.push(new TraceFile(file, parentArchive));
};
this.isLoadingFiles = true;
this.progressMessage = "Unzipping files...";
this.changeDetectorRef.detectChanges();
await FileUtils.unzipFilesIfNeeded(files, onFile, onProgressUpdate);
this.progressMessage = "Parsing files...";
this.changeDetectorRef.detectChanges();
const parserErrors = await this.traceData.loadTraces(traceFiles, onProgressUpdate);
this.isLoadingFiles = false;
this.changeDetectorRef.detectChanges();
ParserErrorSnackBarComponent.showIfNeeded(this.ngZone, this.snackBar, parserErrors);
}
public onViewTracesButtonClick() {
this.traceDataLoaded.emit();
}
@@ -277,6 +244,43 @@ export class UploadTracesComponent implements UploadTracesComponentDependencyInv
this.changeDetectorRef.detectChanges();
}
private async processFiles(files: File[]) {
const UI_PROGRESS_UPDATE_PERIOD_MS = 200;
let lastUiProgressUpdate = Date.now();
const onProgressUpdate = (progress: number) => {
const now = Date.now();
if ((Date.now() - lastUiProgressUpdate) < UI_PROGRESS_UPDATE_PERIOD_MS) {
// Let's limit the amount of UI updates, because the progress bar component
// renders weird stuff when updated too frequently
return;
}
lastUiProgressUpdate = now;
this.progressPercentage = progress;
this.changeDetectorRef.detectChanges();
};
const traceFiles: TraceFile[] = [];
const onFile: OnFile = (file: File, parentArchive?: File) => {
traceFiles.push(new TraceFile(file, parentArchive));
};
this.isLoadingFiles = true;
this.progressMessage = "Unzipping files...";
this.changeDetectorRef.detectChanges();
await FileUtils.unzipFilesIfNeeded(files, onFile, onProgressUpdate);
this.progressMessage = "Parsing files...";
this.changeDetectorRef.detectChanges();
const parserErrors = await this.traceData.loadTraces(traceFiles, onProgressUpdate);
this.isLoadingFiles = false;
this.changeDetectorRef.detectChanges();
ParserErrorSnackBarComponent.showIfNeeded(this.ngZone, this.snackBar, parserErrors);
}
private getInputFiles(event: Event): File[] {
const files: FileList | null = (event?.target as HTMLInputElement)?.files;
if (!files || !files[0]) {

View File

@@ -14,14 +14,14 @@
* limitations under the License.
*/
import {UploadTracesComponentDependencyInversion} from "./upload_traces_component_dependency_inversion";
import {FilesDownloadListener} from "interfaces/files_download_listener";
export class UploadTracesComponentStub implements UploadTracesComponentDependencyInversion {
export class UploadTracesComponentStub implements FilesDownloadListener {
onFilesDownloadStart() {
// do nothing
}
async processFiles(files: File[]) {
async onFilesDownloaded(files: File[]) {
// do nothing
}
}

View File

@@ -93,18 +93,18 @@ describe("Mediator", () => {
spyOn(uploadTracesComponent, "onFilesDownloadStart");
expect(uploadTracesComponent.onFilesDownloadStart).toHaveBeenCalledTimes(0);
abtChromeExtensionProtocol.onBugAttachmentsDownloadStart();
abtChromeExtensionProtocol.onBuganizerAttachmentsDownloadStart();
expect(uploadTracesComponent.onFilesDownloadStart).toHaveBeenCalledTimes(1);
});
it("handles empty downloaded files from ABT chrome extension", async () => {
spyOn(uploadTracesComponent, "processFiles");
expect(uploadTracesComponent.processFiles).toHaveBeenCalledTimes(0);
spyOn(uploadTracesComponent, "onFilesDownloaded");
expect(uploadTracesComponent.onFilesDownloaded).toHaveBeenCalledTimes(0);
// Pass files even if empty so that the upload component will update the progress bar
// and display error messages
await abtChromeExtensionProtocol.onBugAttachmentsReceived([]);
expect(uploadTracesComponent.processFiles).toHaveBeenCalledTimes(1);
await abtChromeExtensionProtocol.onBuganizerAttachmentsDownloaded([]);
expect(uploadTracesComponent.onFilesDownloaded).toHaveBeenCalledTimes(1);
});
it("propagates current timestamp changed through timeline", async () => {

View File

@@ -14,21 +14,29 @@
* limitations under the License.
*/
import {AppComponentDependencyInversion} from "./components/app_component_dependency_inversion";
import {TimelineComponentDependencyInversion}
from "./components/timeline/timeline_component_dependency_inversion";
import {UploadTracesComponentDependencyInversion} from "./components/upload_traces_component_dependency_inversion";
import {TimelineData} from "./timeline_data";
import {TraceData} from "./trace_data";
import {AbtChromeExtensionProtocolDependencyInversion}
from "abt_chrome_extension/abt_chrome_extension_protocol_dependency_inversion";
import {CrossToolProtocolDependencyInversion}
from "cross_tool/cross_tool_protocol_dependency_inversion";
import {Timestamp, TimestampType} from "common/trace/timestamp";
import {TraceType} from "common/trace/trace_type";
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 {TimestampChangeListener} from "interfaces/timestamp_change_listener";
import {TraceDataListener} from "interfaces/trace_data_listener";
import {Viewer} from "viewers/viewer";
import {ViewerFactory} from "viewers/viewer_factory";
export type CrossToolProtocolDependencyInversion =
RemoteBugreportReceiver & RemoteTimestampReceiver & RemoteTimestampSender;
export type AbtChromeExtensionProtocolDependencyInversion =
BuganizerAttachmentsDownloadEmitter & Runnable;
export type AppComponentDependencyInversion = TraceDataListener;
export type TimelineComponentDependencyInversion = TimestampChangeListener;
export type UploadTracesComponentDependencyInversion = FilesDownloadListener;
export class Mediator {
private abtChromeExtensionProtocol: AbtChromeExtensionProtocolDependencyInversion;
private crossToolProtocol: CrossToolProtocolDependencyInversion;
@@ -64,20 +72,19 @@ export class Mediator {
});
this.crossToolProtocol.setOnBugreportReceived(async (bugreport: File, timestamp?: Timestamp) => {
await this.onRemoteToolBugreportReceived(bugreport, timestamp);
await this.onRemoteBugreportReceived(bugreport, timestamp);
});
this.crossToolProtocol.setOnTimestampReceived(async (timestamp: Timestamp) => {
this.onRemoteToolTimestampReceived(timestamp);
this.onRemoteTimestampReceived(timestamp);
});
this.abtChromeExtensionProtocol.setOnBugAttachmentsReceived(async (attachments: File[]) => {
await this.onAbtChromeExtensionBugAttachmentsReceived(attachments);
this.abtChromeExtensionProtocol.setOnBuganizerAttachmentsDownloadStart(() => {
this.onBuganizerAttachmentsDownloadStart();
});
this.abtChromeExtensionProtocol.setOnBugAttachmentsDownloadStart(() => {
console.log("Mediator notifying onFilesDownloadStart()");
this.uploadTracesComponent?.onFilesDownloadStart();
this.abtChromeExtensionProtocol.setOnBuganizerAttachmentsDownloaded(async (attachments: File[]) => {
await this.onBuganizerAttachmentsDownloaded(attachments);
});
}
@@ -95,21 +102,14 @@ export class Mediator {
this.abtChromeExtensionProtocol.run();
}
public onWinscopeUploadNew() {
this.resetAppToInitialState();
}
public onWinscopeTraceDataLoaded() {
this.processTraceData();
}
public async onRemoteToolBugreportReceived(bugreport: File, timestamp?: Timestamp) {
await this.processRemoteFilesReceived([bugreport]);
if (timestamp !== undefined) {
this.onRemoteToolTimestampReceived(timestamp);
}
}
public async onAbtChromeExtensionBugAttachmentsReceived(attachments: File[]) {
await this.processRemoteFilesReceived(attachments);
}
public onWinscopeCurrentTimestampChanged(timestamp: Timestamp|undefined) {
this.executeIgnoringRecursiveTimestampNotifications(() => {
const entries = this.traceData.getTraceEntries(timestamp);
@@ -133,7 +133,23 @@ export class Mediator {
});
}
public onRemoteToolTimestampReceived(timestamp: Timestamp) {
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;
@@ -164,15 +180,9 @@ export class Mediator {
});
}
public onWinscopeUploadNew() {
this.reset();
}
private async processRemoteFilesReceived(files: File[]) {
this.appComponent.onUploadNewClick();
this.traceData.clear();
this.uploadTracesComponent?.processFiles(files); // will notify back "trace data loaded"
this.isTraceDataVisualized = false;
this.resetAppToInitialState();
this.uploadTracesComponent?.onFilesDownloaded(files);
}
private processTraceData() {
@@ -185,7 +195,7 @@ export class Mediator {
this.isTraceDataVisualized = true;
if (this.lastRemoteToolTimestampReceived !== undefined) {
this.onRemoteToolTimestampReceived(this.lastRemoteToolTimestampReceived);
this.onRemoteTimestampReceived(this.lastRemoteToolTimestampReceived);
}
}
@@ -211,12 +221,12 @@ export class Mediator {
}
}
private reset() {
private resetAppToInitialState() {
this.traceData.clear();
this.timelineData.clear();
this.viewers = [];
this.isChangingCurrentTimestamp = false;
this.isTraceDataVisualized = false;
this.lastRemoteToolTimestampReceived = undefined;
this.appComponent.onTraceDataUnloaded();
}
}

View File

@@ -18,6 +18,7 @@ import {
toSize, toActiveBuffer, toColor, toColor3, toPoint, toPointF, toRect,
toRectF, toRegion, toMatrix22, toTransform, toInsets
} from './common';
import {ArrayUtils} from "common/utils/array_utils";
import { PropertiesDump } from "viewers/common/ui_tree_utils";
import intDefMapping from
'../../../../../../../prebuilts/misc/common/winscope/intDefMapping.json';
@@ -176,6 +177,16 @@ export default class ObjectFormatter {
}
}
// Raw long number (no type name, no constructor name, no useful toString() method)
if (ArrayUtils.equal(Object.keys(obj).sort(), ["high_", "low_"])) {
const high = BigInt(obj.high_) << 32n;
let low = BigInt(obj.low_);
if (low < 0) {
low = -low;
}
return (high | low).toString();
}
return null;
}

View File

@@ -21,8 +21,8 @@ import { TimeUtils } from "common/utils/time_utils";
import { ElapsedTimestamp, RealTimestamp } from "common/trace/timestamp";
LayerTraceEntry.fromProto = function (
protos: any[],
displayProtos: any[],
protos: object[],
displayProtos: object[],
elapsedTimestamp: bigint,
vSyncId: number,
hwcBlob: string,
@@ -49,7 +49,7 @@ LayerTraceEntry.fromProto = function (
return entry;
}
function addAttributes(entry: LayerTraceEntry, protos: any, useElapsedTime = false) {
function addAttributes(entry: LayerTraceEntry, protos: object[], useElapsedTime = false) {
entry.kind = "entry";
// Avoid parsing the entry root because it is an array of layers
// containing all trace information, this slows down the property tree.

View File

@@ -16,12 +16,11 @@
import {Message, MessageBugReport, MessagePong, MessageTimestamp, MessageType} from "./messages";
import {OriginAllowList} from "./origin_allow_list";
import {
CrossToolProtocolDependencyInversion,
OnBugreportReceived,
OnTimestampReceived} from "cross_tool/cross_tool_protocol_dependency_inversion";
import {RealTimestamp} from "common/trace/timestamp";
import {FunctionUtils} from "common/utils/function_utils";
import {RemoteBugreportReceiver, OnBugreportReceived} from "interfaces/remote_bugreport_receiver";
import {RemoteTimestampReceiver, OnTimestampReceived} from "interfaces/remote_timestamp_receiver";
import {RemoteTimestampSender} from "interfaces/remote_timestamp_sender";
class RemoteTool {
constructor(
@@ -30,7 +29,10 @@ class RemoteTool {
}
}
export class CrossToolProtocol implements CrossToolProtocolDependencyInversion {
export class CrossToolProtocol implements
RemoteBugreportReceiver,
RemoteTimestampReceiver,
RemoteTimestampSender {
private remoteTool?: RemoteTool;
private onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
private onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING;

View File

@@ -14,14 +14,16 @@
* limitations under the License.
*/
import {
CrossToolProtocolDependencyInversion,
OnBugreportReceived,
OnTimestampReceived} from "cross_tool/cross_tool_protocol_dependency_inversion";
import {RealTimestamp} from "common/trace/timestamp";
import {FunctionUtils} from "common/utils/function_utils";
import {RemoteBugreportReceiver, OnBugreportReceived} from "interfaces/remote_bugreport_receiver";
import {RemoteTimestampReceiver, OnTimestampReceived} from "interfaces/remote_timestamp_receiver";
import {RemoteTimestampSender} from "interfaces/remote_timestamp_sender";
export class CrossToolProtocolStub implements CrossToolProtocolDependencyInversion {
export class CrossToolProtocolStub implements
RemoteBugreportReceiver,
RemoteTimestampReceiver,
RemoteTimestampSender {
onBugreportReceived: OnBugreportReceived = FunctionUtils.DO_NOTHING_ASYNC;
onTimestampReceived: OnTimestampReceived = FunctionUtils.DO_NOTHING;

View File

@@ -0,0 +1,23 @@
/*
* 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.
*/
export type OnBuganizerAttachmentsDownloadStart = () => void;
export type OnBuganizerAttachmentsDownloaded = (attachments: File[]) => Promise<void>;
export interface BuganizerAttachmentsDownloadEmitter {
setOnBuganizerAttachmentsDownloadStart(callback: OnBuganizerAttachmentsDownloadStart): void;
setOnBuganizerAttachmentsDownloaded(callback: OnBuganizerAttachmentsDownloaded): void;
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
export interface UploadTracesComponentDependencyInversion {
export interface FilesDownloadListener {
onFilesDownloadStart(): void;
processFiles(files: File[]): Promise<void>;
onFilesDownloaded(files: File[]): Promise<void>;
}

View File

@@ -17,10 +17,7 @@
import {RealTimestamp} from "common/trace/timestamp";
export type OnBugreportReceived = (bugreport: File, timestamp?: RealTimestamp) => Promise<void>;
export type OnTimestampReceived = (timestamp: RealTimestamp) => void;
export interface CrossToolProtocolDependencyInversion {
export interface RemoteBugreportReceiver {
setOnBugreportReceived(callback: OnBugreportReceived): void;
setOnTimestampReceived(callback: OnTimestampReceived): void;
sendTimestamp(timestamp: RealTimestamp): void;
}

View File

@@ -0,0 +1,23 @@
/*
* 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 {RealTimestamp} from "common/trace/timestamp";
export type OnTimestampReceived = (timestamp: RealTimestamp) => void;
export interface RemoteTimestampReceiver {
setOnTimestampReceived(callback: OnTimestampReceived): void;
}

View File

@@ -0,0 +1,21 @@
/*
* 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 {RealTimestamp} from "common/trace/timestamp";
export interface RemoteTimestampSender {
sendTimestamp(timestamp: RealTimestamp): void;
}

View File

@@ -14,11 +14,6 @@
* limitations under the License.
*/
export type OnBugAttachmentsDownloadStart = () => void;
export type OnBugAttachmentsReceived = (attachments: File[]) => Promise<void>;
export interface AbtChromeExtensionProtocolDependencyInversion {
setOnBugAttachmentsDownloadStart(callback: OnBugAttachmentsDownloadStart): void;
setOnBugAttachmentsReceived(callback: OnBugAttachmentsReceived): void;
export interface Runnable {
run(): void;
}

View File

@@ -16,6 +16,6 @@
import {Timestamp} from "common/trace/timestamp";
export interface TimelineComponentDependencyInversion {
export interface TimestampChangeListener {
onCurrentTimestampChanged(timestamp: Timestamp|undefined): void;
}

View File

@@ -16,7 +16,7 @@
import {Viewer} from "viewers/viewer";
export interface AppComponentDependencyInversion {
export interface TraceDataListener {
onTraceDataUnloaded(): void;
onTraceDataLoaded(viewers: Viewer[]): void;
onUploadNewClick(): void;
}

View File

@@ -39,7 +39,3 @@ app-root {
flex-direction: row;
overflow: auto;
}
viewer-surface-flinger .properties-view .view-header {
flex: 3;
}

View File

@@ -22,7 +22,8 @@ import { PropertiesTreeNode, Terminal} from "viewers/common/ui_tree_utils";
@Component({
selector: "properties-view",
template: `
<div class="view-header">
<div class="view-header"
[class.view-header-with-property-groups]="displayPropertyGroups">
<div class="title-filter">
<h2 class="properties-title mat-title">Properties</h2>
@@ -49,7 +50,7 @@ import { PropertiesTreeNode, Terminal} from "viewers/common/ui_tree_utils";
</div>
<property-groups
*ngIf="itemIsSelected() && propertyGroups"
*ngIf="itemIsSelected() && displayPropertyGroups"
class="property-groups"
[item]="selectedFlickerItem"
></property-groups>
@@ -58,7 +59,8 @@ import { PropertiesTreeNode, Terminal} from "viewers/common/ui_tree_utils";
<mat-divider></mat-divider>
<div class="properties-content">
<h3 *ngIf="objectKeys(propertiesTree).length > 0 && isProtoDump" class="properties-title mat-subheading-2">Properties - Proto Dump</h3>
<h3 *ngIf="objectKeys(propertiesTree).length > 0 && isProtoDump"
class="properties-title mat-subheading-2">Properties - Proto Dump</h3>
<div class="tree-wrapper">
<tree-view
@@ -80,6 +82,10 @@ import { PropertiesTreeNode, Terminal} from "viewers/common/ui_tree_utils";
margin-bottom: 12px;
}
.view-header-with-property-groups {
flex: 3;
}
.title-filter {
display: flex;
flex-direction: row;
@@ -121,7 +127,7 @@ export class PropertiesComponent {
@Input() userOptions: UserOptions = {};
@Input() propertiesTree: PropertiesTreeNode = {};
@Input() selectedFlickerItem: TraceTreeNode | null = null;
@Input() propertyGroups = false;
@Input() displayPropertyGroups = false;
@Input() isProtoDump = false;
constructor(

View File

@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, Inject, Input, Output, ElementRef, EventEmitter, ChangeDetectionStrategy } from "@angular/core";
import {Component, Inject, Input, Output, ElementRef, EventEmitter, ChangeDetectionStrategy} from "@angular/core";
import { PersistentStore } from "common/utils/persistent_store";
import { nodeStyles, treeNodeDataViewStyles } from "viewers/components/styles/node.styles";
import { UiTreeUtils, UiTreeNode, HierarchyTreeNode, PropertiesTreeNode } from "viewers/common/ui_tree_utils";
import { UiTreeUtils, UiTreeNode, HierarchyTreeNode } from "viewers/common/ui_tree_utils";
import { TraceType } from "common/trace/trace_type";
@Component({
@@ -76,8 +76,12 @@ export class TreeComponent {
diffClass = UiTreeUtils.diffClass;
isHighlighted = UiTreeUtils.isHighlighted;
@Input() item?: UiTreeNode;
// TODO (b/263779536): this array is passed down from viewers/presenters and is used to generate
// an identifier supposed to be unique for each viewer. Let's just use a proper identifier
// instead. Each viewer/presenter could pass down a random magic number, an UUID, ...
@Input() dependencies: Array<TraceType> = [];
@Input() item?: UiTreeNode;
@Input() store!: PersistentStore;
@Input() isFlattened? = false;
@Input() initialDepth = 0;
@@ -117,7 +121,7 @@ export class TreeComponent {
}
constructor(
@Inject(ElementRef) public elementRef: ElementRef,
@Inject(ElementRef) public elementRef: ElementRef
) {
this.nodeElement = elementRef.nativeElement.querySelector(".node");
this.nodeElement?.addEventListener("mousedown", this.nodeMouseDownEventListener);
@@ -168,18 +172,14 @@ export class TreeComponent {
}
private updateHighlightedItems() {
if (this.item instanceof HierarchyTreeNode) {
if (this.item.stableId) {
this.highlightedItemChange.emit(`${this.item.stableId}`);
} else if (!this.item.stableId) {
//this.selectedTreeChange.emit(this.item);
}
if (this.item?.stableId) {
this.highlightedItemChange.emit(`${this.item.stableId}`);
}
}
public isPinned() {
if (this.item instanceof HierarchyTreeNode) {
return this.pinnedItems?.map(item => `${item.id}`).includes(`${this.item.id}`);
return this.pinnedItems?.map(item => `${item.stableId}`).includes(`${this.item.stableId}`);
}
return false;
}

View File

@@ -1,4 +1,3 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
@@ -31,7 +30,7 @@ export class Presenter {
constructor(notifyViewCallback: NotifyViewCallbackType, private storage: Storage) {
this.notifyViewCallback = notifyViewCallback;
this.uiData = new UiData([TraceType.SURFACE_FLINGER]);
this.notifyViewCallback(this.uiData);
this.copyUiDataAndNotifyView();
}
public updatePinnedItems(pinnedItem: HierarchyTreeNode) {
@@ -43,7 +42,7 @@ export class Presenter {
}
this.updatePinnedIds(pinnedId);
this.uiData.pinnedItems = this.pinnedItems;
this.notifyViewCallback(this.uiData);
this.copyUiDataAndNotifyView();
}
public updateHighlightedItems(id: string) {
@@ -54,20 +53,20 @@ export class Presenter {
this.highlightedItems.push(id);
}
this.uiData.highlightedItems = this.highlightedItems;
this.notifyViewCallback(this.uiData);
this.copyUiDataAndNotifyView();
}
public updateHierarchyTree(userOptions: UserOptions) {
this.hierarchyUserOptions = userOptions;
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.tree = this.generateTree();
this.notifyViewCallback(this.uiData);
this.copyUiDataAndNotifyView();
}
public filterHierarchyTree(filterString: string) {
this.hierarchyFilter = TreeUtils.makeNodeFilter(filterString);
this.uiData.tree = this.generateTree();
this.notifyViewCallback(this.uiData);
this.copyUiDataAndNotifyView();
}
public updatePropertiesTree(userOptions: UserOptions) {
@@ -101,7 +100,7 @@ export class Presenter {
this.uiData.tree = this.generateTree();
}
}
this.notifyViewCallback(this.uiData);
this.copyUiDataAndNotifyView();
}
private generateRects(): Rectangle[] {
@@ -162,8 +161,9 @@ export class Presenter {
if (this.selectedHierarchyTree) {
this.uiData.propertiesTree = this.getTreeWithTransformedProperties(this.selectedHierarchyTree);
this.uiData.selectedLayer = this.selectedLayer;
this.uiData.displayPropertyGroups = this.shouldDisplayPropertyGroups(this.selectedLayer);
}
this.notifyViewCallback(this.uiData);
this.copyUiDataAndNotifyView();
}
private generateTree() {
@@ -253,6 +253,20 @@ export class Presenter {
return transformedTree;
}
private shouldDisplayPropertyGroups(selectedLayer: Layer): boolean {
// Do not display property groups when the root layer is selected. The root layer doesn't
// provide property groups info (visibility, geometry transforms, ...).
const isRoot = selectedLayer === this.entry;
return !isRoot;
}
private copyUiDataAndNotifyView() {
// Create a shallow copy of the data, otherwise the Angular OnPush change detection strategy
// won't detect the new input
const copy = Object.assign({}, this.uiData);
this.notifyViewCallback(copy);
}
private readonly notifyViewCallback: NotifyViewCallbackType;
private uiData: UiData;
private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter("");

View File

@@ -30,6 +30,7 @@ export class UiData {
tree: HierarchyTreeNode | null = null;
propertiesTree: PropertiesTreeNode | null = null;
selectedLayer: Layer = {};
displayPropertyGroups = true;
constructor(dependencies?: Array<TraceType>) {
this.dependencies = dependencies ?? [];

View File

@@ -14,6 +14,7 @@
* limitations under the License.
*/
import {
ChangeDetectionStrategy,
Component,
Input,
} from "@angular/core";
@@ -24,6 +25,7 @@ import { PersistentStore } from "common/utils/persistent_store";
@Component({
selector: "viewer-surface-flinger",
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<div class="card-grid">
<rects-view
@@ -49,7 +51,7 @@ import { PersistentStore } from "common/utils/persistent_store";
[userOptions]="inputData?.propertiesUserOptions ?? {}"
[propertiesTree]="inputData?.propertiesTree ?? {}"
[selectedFlickerItem]="inputData?.selectedLayer ?? {}"
[propertyGroups]="true"
[displayPropertyGroups]="inputData?.displayPropertyGroups"
[isProtoDump]="true"
></properties-view>
</div>
@@ -67,7 +69,7 @@ import { PersistentStore } from "common/utils/persistent_store";
]
})
export class ViewerSurfaceFlingerComponent {
@Input() inputData: UiData | null = null;
@Input() inputData?: UiData;
@Input() store: PersistentStore = new PersistentStore();
@Input() active = false;
TRACE_INFO = TRACE_INFO;

View File

@@ -13,22 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {TraceType} from "common/trace/trace_type";
import {View, Viewer, ViewType} from "viewers/viewer";
import {Presenter} from "./presenter";
import {UiData} from "./ui_data";
import {TraceType} from "common/trace/trace_type";
import {View, Viewer, ViewType} from "viewers/viewer";
import {ViewerEvents} from "viewers/common/viewer_events";
class ViewerSurfaceFlinger implements Viewer {
public static readonly DEPENDENCIES: TraceType[] = [TraceType.SURFACE_FLINGER];
private htmlElement: HTMLElement;
private presenter: Presenter;
constructor(storage: Storage) {
this.htmlElement = document.createElement("viewer-surface-flinger");
this.presenter = new Presenter((uiData: UiData) => {
// Angular does not deep watch @Input properties. Clearing inputData to null before repopulating
// automatically ensures that the UI will change via the Angular change detection cycle. Without
// resetting, Angular does not auto-detect that inputData has changed.
(this.htmlElement as any).inputData = null;
(this.htmlElement as any).inputData = uiData;
}, storage);
this.htmlElement.addEventListener(ViewerEvents.HierarchyPinnedChange, (event) => this.presenter.updatePinnedItems(((event as CustomEvent).detail.pinnedItem)));
this.htmlElement.addEventListener(ViewerEvents.HighlightedChange, (event) => this.presenter.updateHighlightedItems(`${(event as CustomEvent).detail.id}`));
this.htmlElement.addEventListener(ViewerEvents.HierarchyUserOptionsChange, (event) => this.presenter.updateHierarchyTree((event as CustomEvent).detail.userOptions));
@@ -49,10 +51,6 @@ class ViewerSurfaceFlinger implements Viewer {
public getDependencies(): TraceType[] {
return ViewerSurfaceFlinger.DEPENDENCIES;
}
public static readonly DEPENDENCIES: TraceType[] = [TraceType.SURFACE_FLINGER];
private htmlElement: HTMLElement;
private presenter: Presenter;
}
export {ViewerSurfaceFlinger};

View File

@@ -65,7 +65,7 @@ import { PersistentStore } from "common/utils/persistent_store";
]
})
export class ViewerWindowManagerComponent {
@Input() inputData: UiData | null = null;
@Input() inputData?: UiData;
@Input() store: PersistentStore = new PersistentStore();
@Input() active = false;
TRACE_INFO = TRACE_INFO;