diff --git a/tools/winscope-ng/package-lock.json b/tools/winscope-ng/package-lock.json
index 5b94d1ca0..b06f3cb0c 100644
--- a/tools/winscope-ng/package-lock.json
+++ b/tools/winscope-ng/package-lock.json
@@ -23,11 +23,14 @@
"@ngrx/store": "^14.0.2",
"@ngxs/store": "^3.7.4",
"@types/jsbn": "^1.2.30",
+ "@types/three": "^0.143.0",
"angular2-template-loader": "^0.6.2",
"auth0": "^2.42.0",
+ "gl-matrix": "^3.4.3",
"html-loader": "^3.1.0",
"html-webpack-inline-source-plugin": "^1.0.0-beta.2",
"html-webpack-plugin": "^5.5.0",
+ "html2canvas": "^1.4.1",
"jsbn": "^1.1.0",
"jsbn-rsa": "^1.0.4",
"kotlin": "^1.7.0",
@@ -62,7 +65,10 @@
"karma-jasmine": "~5.0.0",
"karma-sourcemap-loader": "^0.3.8",
"karma-webpack": "^5.0.0",
- "protractor": "^7.0.0"
+ "protractor": "^7.0.0",
+ "three": "^0.143.0",
+ "webgl-utils": "^1.0.1",
+ "webgl-utils.js": "^1.1.0"
}
},
"node_modules/@ampproject/remapping": {
@@ -3491,12 +3497,25 @@
"@types/node": "*"
}
},
+ "node_modules/@types/three": {
+ "version": "0.143.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.143.0.tgz",
+ "integrity": "sha512-c5PonXOt8xk5q4ygmyjOX4Ec+FA7gwfdcMT/PveE9xrJs/0DDcf2lJkWrhEcmvx2ZefQCQBcogABnGqB0P4OsA==",
+ "dependencies": {
+ "@types/webxr": "*"
+ }
+ },
"node_modules/@types/w3c-web-usb": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz",
"integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==",
"dev": true
},
+ "node_modules/@types/webxr": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
+ "integrity": "sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA=="
+ },
"node_modules/@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -4442,6 +4461,14 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -5611,6 +5638,14 @@
"postcss": "^8.4"
}
},
+ "node_modules/css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/css-loader": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
@@ -7729,6 +7764,11 @@
"assert-plus": "^1.0.0"
}
},
+ "node_modules/gl-matrix": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
+ "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
+ },
"node_modules/glob": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.1.tgz",
@@ -8079,6 +8119,18 @@
"webpack": "^5.20.0"
}
},
+ "node_modules/html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "dependencies": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/htmlparser2": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -14399,12 +14451,26 @@
"node": "*"
}
},
+ "node_modules/text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/three": {
+ "version": "0.143.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.143.0.tgz",
+ "integrity": "sha512-oKcAGYHhJ46TGEuHjodo2n6TY2R6lbvrkp+feKZxqsUL/WkH7GKKaeu6RHeyb2Xjfk2dPLRKLsOP0KM2VgT8Zg==",
+ "dev": true
+ },
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -14843,6 +14909,14 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
@@ -15104,6 +15178,18 @@
"node": ">=0.8.0"
}
},
+ "node_modules/webgl-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/webgl-utils/-/webgl-utils-1.0.1.tgz",
+ "integrity": "sha512-ox5xQ3YkrrOR6pCZHTOud49zzMXP9LnXzx7jIQvHOinV4FK59rGGJURw2Lq1cCTPgMVU2wAWq7e6vEVjz9FdBw==",
+ "dev": true
+ },
+ "node_modules/webgl-utils.js": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/webgl-utils.js/-/webgl-utils.js-1.1.0.tgz",
+ "integrity": "sha512-cmO2aPd6gR6bK/ttdk8ZIypJfZMOcTvsvXv/LxXZjAFu5TC6vXqFrZYudlPuKxVsA34Pc8Fysq2rCnflu+wuuA==",
+ "dev": true
+ },
"node_modules/webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
@@ -18141,12 +18227,25 @@
"@types/node": "*"
}
},
+ "@types/three": {
+ "version": "0.143.0",
+ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.143.0.tgz",
+ "integrity": "sha512-c5PonXOt8xk5q4ygmyjOX4Ec+FA7gwfdcMT/PveE9xrJs/0DDcf2lJkWrhEcmvx2ZefQCQBcogABnGqB0P4OsA==",
+ "requires": {
+ "@types/webxr": "*"
+ }
+ },
"@types/w3c-web-usb": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.6.tgz",
"integrity": "sha512-cSjhgrr8g4KbPnnijAr/KJDNKa/bBa+ixYkywFRvrhvi9n1WEl7yYbtRyzE6jqNQiSxxJxoAW3STaOQwJHndaw==",
"dev": true
},
+ "@types/webxr": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.0.tgz",
+ "integrity": "sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA=="
+ },
"@types/ws": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -18845,6 +18944,11 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="
+ },
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -19749,6 +19853,14 @@
"postcss-selector-parser": "^6.0.9"
}
},
+ "css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "requires": {
+ "utrie": "^1.0.2"
+ }
+ },
"css-loader": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.1.tgz",
@@ -21244,6 +21356,11 @@
"assert-plus": "^1.0.0"
}
},
+ "gl-matrix": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
+ "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA=="
+ },
"glob": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.1.tgz",
@@ -21517,6 +21634,15 @@
"tapable": "^2.0.0"
}
},
+ "html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "requires": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ }
+ },
"htmlparser2": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz",
@@ -26286,12 +26412,26 @@
}
}
},
+ "text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "requires": {
+ "utrie": "^1.0.2"
+ }
+ },
"text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "three": {
+ "version": "0.143.0",
+ "resolved": "https://registry.npmjs.org/three/-/three-0.143.0.tgz",
+ "integrity": "sha512-oKcAGYHhJ46TGEuHjodo2n6TY2R6lbvrkp+feKZxqsUL/WkH7GKKaeu6RHeyb2Xjfk2dPLRKLsOP0KM2VgT8Zg==",
+ "dev": true
+ },
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -26610,6 +26750,14 @@
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"dev": true
},
+ "utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "requires": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
@@ -26816,6 +26964,18 @@
}
}
},
+ "webgl-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/webgl-utils/-/webgl-utils-1.0.1.tgz",
+ "integrity": "sha512-ox5xQ3YkrrOR6pCZHTOud49zzMXP9LnXzx7jIQvHOinV4FK59rGGJURw2Lq1cCTPgMVU2wAWq7e6vEVjz9FdBw==",
+ "dev": true
+ },
+ "webgl-utils.js": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/webgl-utils.js/-/webgl-utils.js-1.1.0.tgz",
+ "integrity": "sha512-cmO2aPd6gR6bK/ttdk8ZIypJfZMOcTvsvXv/LxXZjAFu5TC6vXqFrZYudlPuKxVsA34Pc8Fysq2rCnflu+wuuA==",
+ "dev": true
+ },
"webidl-conversions": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
diff --git a/tools/winscope-ng/package.json b/tools/winscope-ng/package.json
index 8e0dd14af..e9b759f79 100644
--- a/tools/winscope-ng/package.json
+++ b/tools/winscope-ng/package.json
@@ -31,11 +31,14 @@
"@ngrx/store": "^14.0.2",
"@ngxs/store": "^3.7.4",
"@types/jsbn": "^1.2.30",
+ "@types/three": "^0.143.0",
"angular2-template-loader": "^0.6.2",
"auth0": "^2.42.0",
+ "gl-matrix": "^3.4.3",
"html-loader": "^3.1.0",
"html-webpack-inline-source-plugin": "^1.0.0-beta.2",
"html-webpack-plugin": "^5.5.0",
+ "html2canvas": "^1.4.1",
"jsbn": "^1.1.0",
"jsbn-rsa": "^1.0.4",
"kotlin": "^1.7.0",
@@ -70,6 +73,9 @@
"karma-jasmine": "~5.0.0",
"karma-sourcemap-loader": "^0.3.8",
"karma-webpack": "^5.0.0",
- "protractor": "^7.0.0"
+ "protractor": "^7.0.0",
+ "three": "^0.143.0",
+ "webgl-utils": "^1.0.1",
+ "webgl-utils.js": "^1.1.0"
}
}
diff --git a/tools/winscope-ng/src/app/app.component.ts b/tools/winscope-ng/src/app/app.component.ts
deleted file mode 100644
index d8d0fc8f5..000000000
--- a/tools/winscope-ng/src/app/app.component.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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 {Component, Inject, Injector, Input} from "@angular/core";
-import {createCustomElement} from "@angular/elements";
-import {Timestamp, TimestampType} from "common/trace/timestamp";
-import {PersistentStore} from "common/persistent_store";
-import {ViewerWindowManagerComponent} from "viewers/viewer_window_manager/viewer_window_manager.component";
-import {Core} from "./core";
-import {ProxyState, proxyClient} from "trace_collection/proxy_client";
-import { Viewer } from "viewers/viewer";
-
-@Component({
- selector: "app-root",
- template: `
-
- Winscope Viewer 2.0
-
-
-
-
-
-
-
-
-
-
-
-
-
- Loaded data
-
-
-
-
-
-
-
-
-
-
-
-
-
- `,
- styles: [".home{width: 100%; display:flex; flex-direction: row; overflow: auto;}"]
-})
-export class AppComponent {
- title = "winscope-ng";
- core: Core;
- states = ProxyState;
- store: PersistentStore = new PersistentStore();
- @Input() dataLoaded = false;
- viewersCreated = false;
-
- constructor(
- @Inject(Injector) injector: Injector
- ) {
- this.core = new Core();
- if (!customElements.get("viewer-window-manager")) {
- customElements.define("viewer-window-manager",
- createCustomElement(ViewerWindowManagerComponent, {injector}));
- }
- }
-
- onDataLoadedChange(dataLoaded: boolean) {
- if (dataLoaded && !this.viewersCreated) {
- this.core.createViewers();
- this.createViewerElements();
- const dummyTimestamp = this.core.getTimestamps()[1]; //TODO: get timestamp from time scrub
- this.core.notifyCurrentTimestamp(dummyTimestamp);
- this.viewersCreated = true;
- this.dataLoaded = dataLoaded;
- }
- }
-
- createViewerElements() {
- const viewersDiv = document.querySelector("div#viewers")!;
- viewersDiv.innerHTML = "";
-
- this.core.getViews().forEach((view: HTMLElement) => {
- viewersDiv.appendChild(view);
- });
- }
-
- public notifyCurrentTimestamp() {
- const dummyTimestamp = new Timestamp(TimestampType.ELAPSED, 1000000n);
- this.core.notifyCurrentTimestamp(dummyTimestamp);
- }
-
- public clearData() {
- this.dataLoaded = false;
- this.viewersCreated = false;
- this.core.clearData();
- proxyClient.adbData = [];
- }
-}
diff --git a/tools/winscope-ng/src/app/app.module.ts b/tools/winscope-ng/src/app/app.module.ts
index 659bd16ba..92c3da9d8 100644
--- a/tools/winscope-ng/src/app/app.module.ts
+++ b/tools/winscope-ng/src/app/app.module.ts
@@ -13,27 +13,40 @@ import { MatFormFieldModule } from "@angular/material/form-field";
import { MatIconModule } from "@angular/material/icon";
import { MatInputModule } from "@angular/material/input";
import { MatSelectModule } from "@angular/material/select";
+import { MatRadioModule } from "@angular/material/radio";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { HttpClientModule } from "@angular/common/http";
+import { MatSliderModule } from "@angular/material/slider";
-import { AppComponent } from "./app.component";
+import { AppComponent } from "./components/app.component";
import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
-import { CollectTracesComponent } from "./collect_traces.component";
-import { AdbProxyComponent } from "./adb_proxy.component";
-import { WebAdbComponent } from "./web_adb.component";
-import { TraceConfigComponent } from "./trace_config.component";
-import { UploadTracesComponent } from "./upload_traces.component";
-
+import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
+import { CollectTracesComponent } from "./components/collect_traces.component";
+import { AdbProxyComponent } from "./components/adb_proxy.component";
+import { WebAdbComponent } from "./components/web_adb.component";
+import { TraceConfigComponent } from "./components/trace_config.component";
+import { UploadTracesComponent } from "./components/upload_traces.component";
+import { HierarchyComponent } from "viewers/hierarchy.component";
+import { PropertiesComponent } from "viewers/properties.component";
+import { RectsComponent } from "viewers/rects.component";
+import { TraceViewHeaderComponent } from "./components/trace_view_header.component";
+import { TraceViewComponent } from "./components/trace_view.component";
@NgModule({
declarations: [
AppComponent,
ViewerWindowManagerComponent,
+ ViewerSurfaceFlingerComponent,
CollectTracesComponent,
UploadTracesComponent,
AdbProxyComponent,
WebAdbComponent,
TraceConfigComponent,
+ HierarchyComponent,
+ PropertiesComponent,
+ RectsComponent,
+ TraceViewHeaderComponent,
+ TraceViewComponent
],
imports: [
BrowserModule,
@@ -52,9 +65,10 @@ import { UploadTracesComponent } from "./upload_traces.component";
MatInputModule,
MatSelectModule,
BrowserAnimationsModule,
- HttpClientModule
+ HttpClientModule,
+ MatSliderModule,
+ MatRadioModule
],
- providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
diff --git a/tools/winscope-ng/src/app/adb_proxy.component.spec.ts b/tools/winscope-ng/src/app/components/adb_proxy.component.spec.ts
similarity index 97%
rename from tools/winscope-ng/src/app/adb_proxy.component.spec.ts
rename to tools/winscope-ng/src/app/components/adb_proxy.component.spec.ts
index f048b6b55..1e7187aa6 100644
--- a/tools/winscope-ng/src/app/adb_proxy.component.spec.ts
+++ b/tools/winscope-ng/src/app/components/adb_proxy.component.spec.ts
@@ -16,7 +16,7 @@
import { CommonModule } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { AdbProxyComponent } from "./adb_proxy.component";
-import { proxyClient, ProxyState } from "../trace_collection/proxy_client";
+import { proxyClient, ProxyState } from "trace_collection/proxy_client";
import { MatIconModule } from "@angular/material/icon";
import { MatFormFieldModule } from "@angular/material/form-field";
import { MatInputModule } from "@angular/material/input";
diff --git a/tools/winscope-ng/src/app/adb_proxy.component.ts b/tools/winscope-ng/src/app/components/adb_proxy.component.ts
similarity index 97%
rename from tools/winscope-ng/src/app/adb_proxy.component.ts
rename to tools/winscope-ng/src/app/components/adb_proxy.component.ts
index 81f6c79dd..2f80b5766 100644
--- a/tools/winscope-ng/src/app/adb_proxy.component.ts
+++ b/tools/winscope-ng/src/app/components/adb_proxy.component.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Component, Input, Output, EventEmitter } from "@angular/core";
-import { proxyClient, ProxyClient, ProxyState } from "../trace_collection/proxy_client";
+import { proxyClient, ProxyClient, ProxyState } from "trace_collection/proxy_client";
@Component({
selector: "adb-proxy",
diff --git a/tools/winscope-ng/src/app/app.component.spec.ts b/tools/winscope-ng/src/app/components/app.component.spec.ts
similarity index 88%
rename from tools/winscope-ng/src/app/app.component.spec.ts
rename to tools/winscope-ng/src/app/components/app.component.spec.ts
index a8e00c87d..c1526d03b 100644
--- a/tools/winscope-ng/src/app/app.component.spec.ts
+++ b/tools/winscope-ng/src/app/components/app.component.spec.ts
@@ -28,6 +28,8 @@ import { WebAdbComponent } from "./web_adb.component";
import { TraceConfigComponent } from "./trace_config.component";
import { ComponentFixtureAutoDetect } from "@angular/core/testing";
+import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
+import { MatSliderModule } from "@angular/material/slider";
describe("AppComponent", () => {
@@ -45,6 +47,7 @@ describe("AppComponent", () => {
MatCardModule,
MatButtonModule,
MatGridListModule,
+ MatSliderModule
],
declarations: [
AppComponent,
@@ -53,6 +56,7 @@ describe("AppComponent", () => {
AdbProxyComponent,
WebAdbComponent,
TraceConfigComponent,
+ ViewerSurfaceFlingerComponent
],
}).overrideComponent(AppComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
@@ -71,7 +75,7 @@ describe("AppComponent", () => {
});
it("renders the page title", () => {
- expect(htmlElement.querySelector("#title")?.innerHTML).toContain("Winscope Viewer 2.0");
+ expect(htmlElement.querySelector("#app-title")?.innerHTML).toContain("Winscope Viewer 2.0");
});
it("displays correct elements when no data loaded", async () => {
@@ -87,6 +91,6 @@ describe("AppComponent", () => {
fixture.detectChanges();
expect(htmlElement.querySelector("#collect-traces-card")).toBeFalsy();
expect(htmlElement.querySelector("#upload-traces-card")).toBeFalsy();
- expect(htmlElement.querySelector("#loaded-data-card")).toBeTruthy();
+ expect(htmlElement.querySelector(".viewers.show")).toBeTruthy();
});
});
diff --git a/tools/winscope-ng/src/app/components/app.component.ts b/tools/winscope-ng/src/app/components/app.component.ts
new file mode 100644
index 000000000..b854e370f
--- /dev/null
+++ b/tools/winscope-ng/src/app/components/app.component.ts
@@ -0,0 +1,168 @@
+/*
+ * 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 { Component, Injector, Inject, ViewEncapsulation, Input } from "@angular/core";
+import { createCustomElement } from "@angular/elements";
+import { TraceCoordinator } from "../trace_coordinator";
+import { proxyClient, ProxyState } from "trace_collection/proxy_client";
+import { PersistentStore } from "common/persistent_store";
+import { ViewerWindowManagerComponent } from "viewers/viewer_window_manager/viewer_window_manager.component";
+import { ViewerSurfaceFlingerComponent } from "viewers/viewer_surface_flinger/viewer_surface_flinger.component";
+import { TraceViewComponent } from "./trace_view.component";
+import { Timestamp } from "common/trace/timestamp";
+import { MatSliderChange } from "@angular/material/slider";
+import { Viewer } from "viewers/viewer";
+
+@Component({
+ selector: "app-root",
+ template: `
+
+ Winscope Viewer 2.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ styles: [".time-slider {width: 100%}"],
+ encapsulation: ViewEncapsulation.None
+})
+export class AppComponent {
+ title = "winscope-ng";
+ traceCoordinator: TraceCoordinator;
+ states = ProxyState;
+ store: PersistentStore = new PersistentStore();
+ @Input() dataLoaded = false;
+ viewersCreated = false;
+ currentTimestamp?: Timestamp;
+ currentTimestampIndex = 0;
+ allTimestamps: Timestamp[] = [];
+
+ constructor(
+ @Inject(Injector) injector: Injector
+ ) {
+ this.traceCoordinator = new TraceCoordinator();
+ if (!customElements.get("viewer-window-manager")) {
+ customElements.define("viewer-window-manager",
+ createCustomElement(ViewerWindowManagerComponent, {injector}));
+ }
+ if (!customElements.get("viewer-surface-flinger")) {
+ customElements.define("viewer-surface-flinger",
+ createCustomElement(ViewerSurfaceFlingerComponent, {injector}));
+ }
+ if (!customElements.get("trace-view")) {
+ customElements.define("trace-view",
+ createCustomElement(TraceViewComponent, {injector}));
+ }
+ }
+
+ onDataLoadedChange(dataLoaded: boolean) {
+ if (dataLoaded && !this.viewersCreated) {
+ this.allTimestamps = this.traceCoordinator.getTimestamps();
+ this.traceCoordinator.createViewers();
+ this.createViewerElements();
+ this.currentTimestampIndex = 0;
+ this.notifyCurrentTimestamp();
+ this.viewersCreated = true;
+ this.dataLoaded = dataLoaded;
+ }
+ }
+
+ createViewerElements() {
+ const viewersDiv = document.querySelector("div#viewers")!;
+ viewersDiv.innerHTML = "";
+
+ let cardCounter = 0;
+ this.traceCoordinator.getViewers().forEach((viewer: Viewer) => {
+ const traceView = document.createElement("trace-view");
+ (traceView as any).title = viewer.getTitle();
+ (traceView as any).dependencies = viewer.getDependencies();
+ (traceView as any).showTrace = true;
+ traceView.addEventListener("saveTraces", ($event: any) => {
+ this.traceCoordinator.saveTraces($event.detail);
+ });
+ viewersDiv.appendChild(traceView);
+
+ const traceCard = traceView.querySelector(".trace-card")!;
+ traceCard.id = `card-${cardCounter}`;
+ (traceView as any).cardId = cardCounter;
+ cardCounter++;
+
+ const traceCardContent = traceCard.querySelector(".trace-card-content")!;
+ const view = viewer.getView();
+ (view as any).showTrace = (traceView as any).showTrace;
+ traceCardContent.appendChild(view);
+ });
+ }
+
+ updateCurrentTimestamp(event: MatSliderChange) {
+ if (event.value) {
+ this.currentTimestampIndex = event.value;
+ this.notifyCurrentTimestamp();
+ }
+ }
+
+ public notifyCurrentTimestamp() {
+ this.currentTimestamp = this.allTimestamps[this.currentTimestampIndex];
+ this.traceCoordinator.notifyCurrentTimestamp(this.currentTimestamp);
+ }
+
+ public toggleTimestamp() {
+ if (this.currentTimestampIndex===0) {
+ this.currentTimestampIndex = this.allTimestamps.length-1;
+ } else {
+ this.currentTimestampIndex = 0;
+ }
+ this.notifyCurrentTimestamp();
+ }
+
+ public clearData() {
+ this.dataLoaded = false;
+ this.viewersCreated = false;
+ this.traceCoordinator.clearData();
+ proxyClient.adbData = [];
+ }
+
+ public showViewers() {
+ const isShown = this.dataLoaded ? "show" : "hide";
+ return ["viewers", isShown];
+ }
+}
diff --git a/tools/winscope-ng/src/app/collect_traces.component.spec.ts b/tools/winscope-ng/src/app/components/collect_traces.component.spec.ts
similarity index 99%
rename from tools/winscope-ng/src/app/collect_traces.component.spec.ts
rename to tools/winscope-ng/src/app/components/collect_traces.component.spec.ts
index 7f4189c32..b021204dd 100644
--- a/tools/winscope-ng/src/app/collect_traces.component.spec.ts
+++ b/tools/winscope-ng/src/app/components/collect_traces.component.spec.ts
@@ -24,6 +24,7 @@ import { MatListModule } from "@angular/material/list";
import { MatButtonModule } from "@angular/material/button";
import { MatProgressBarModule } from "@angular/material/progress-bar";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
+import { NO_ERRORS_SCHEMA } from "@angular/core";
describe("CollectTracesComponent", () => {
let fixture: ComponentFixture;
@@ -36,7 +37,6 @@ describe("CollectTracesComponent", () => {
MatIconModule,
MatCardModule,
MatListModule,
- MatIconModule,
MatButtonModule,
MatProgressBarModule,
BrowserAnimationsModule
@@ -47,6 +47,7 @@ describe("CollectTracesComponent", () => {
WebAdbComponent,
TraceConfigComponent,
],
+ schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
fixture = TestBed.createComponent(CollectTracesComponent);
component = fixture.componentInstance;
diff --git a/tools/winscope-ng/src/app/collect_traces.component.ts b/tools/winscope-ng/src/app/components/collect_traces.component.ts
similarity index 87%
rename from tools/winscope-ng/src/app/collect_traces.component.ts
rename to tools/winscope-ng/src/app/components/collect_traces.component.ts
index aaffc66bf..c9939bb4f 100644
--- a/tools/winscope-ng/src/app/collect_traces.component.ts
+++ b/tools/winscope-ng/src/app/components/collect_traces.component.ts
@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, Input, OnInit, Output, EventEmitter, NgZone, Inject } from "@angular/core";
+import { Component, Inject, Input, Output, EventEmitter, OnInit, OnDestroy } from "@angular/core";
import { ProxyConnection } from "trace_collection/proxy_connection";
import { Connection } from "trace_collection/connection";
import { setTraces } from "trace_collection/set_traces";
-import { ProxyState } from "../trace_collection/proxy_client";
-import { traceConfigurations, configMap, SelectionConfiguration, EnableConfiguration } from "../trace_collection/trace_collection_utils";
-import { Core } from "app/core";
-import { PersistentStore } from "../common/persistent_store";
+import { ProxyState } from "trace_collection/proxy_client";
+import { traceConfigurations, configMap, SelectionConfiguration, EnableConfiguration } from "trace_collection/trace_collection_utils";
+import { TraceCoordinator } from "app/trace_coordinator";
+import { PersistentStore } from "common/persistent_store";
@Component({
@@ -33,9 +33,9 @@ import { PersistentStore } from "../common/persistent_store";
-
+
-
+
@@ -110,30 +110,26 @@ import { PersistentStore } from "../common/persistent_store";
`,
- styles: [".device-choice {cursor: pointer}"]
+ styles: [
+ ".device-choice {cursor: pointer}",
+ ".mat-checkbox .mat-checkbox-frame {transform: scale(0.7); font-size: 10;}",
+ ".mat-checkbox-checked .mat-checkbox-background {transform: scale(0.7); font-size: 10;}"
+ ]
})
-export class CollectTracesComponent implements OnInit {
+export class CollectTracesComponent implements OnInit, OnDestroy {
objectKeys = Object.keys;
isAdbProxy = true;
traceConfigurations = traceConfigurations;
connect: Connection = new ProxyConnection();
setTraces = setTraces;
-
- @Input()
- store: PersistentStore = new PersistentStore();
-
- @Input()
- core?: Core;
-
- @Output()
- coreChange = new EventEmitter
();
-
dataLoaded = false;
- @Output()
- dataLoadedChange = new EventEmitter();
+ @Input() store!: PersistentStore;
+ @Input() traceCoordinator!: TraceCoordinator;
- ngOnInit(): void {
+ @Output() dataLoadedChange = new EventEmitter();
+
+ ngOnInit() {
if (this.isAdbProxy) {
this.connect = new ProxyConnection();
} else {
@@ -142,8 +138,6 @@ export class CollectTracesComponent implements OnInit {
}
}
- constructor(@Inject(NgZone) private ngZone: NgZone) {}
-
ngOnDestroy(): void {
this.connect.proxy?.removeOnProxyChange(this.onProxyChange);
}
@@ -258,7 +252,7 @@ export class CollectTracesComponent implements OnInit {
if (!setTraces.dumpError) {
await this.loadFiles();
} else {
- this.core?.clearData();
+ this.traceCoordinator.clearData();
}
}
@@ -273,13 +267,12 @@ export class CollectTracesComponent implements OnInit {
public async loadFiles() {
console.log("loading files", this.connect.adbData());
- this.core?.clearData();
- await this.core?.addTraces(this.connect.adbData());
- this.ngZone.run(() => {
- this.dataLoaded = true;
- this.dataLoadedChange.emit(this.dataLoaded);
- console.log("finished loading data!");
- });
+ this.traceCoordinator.clearData();
+
+ await this.traceCoordinator.addTraces(this.connect.adbData());
+ this.dataLoaded = true;
+ this.dataLoadedChange.emit(this.dataLoaded);
+ console.log("finished loading data!");
}
public tabClass(adbTab: boolean) {
diff --git a/tools/winscope-ng/src/app/trace_config.component.spec.ts b/tools/winscope-ng/src/app/components/trace_config.component.spec.ts
similarity index 98%
rename from tools/winscope-ng/src/app/trace_config.component.spec.ts
rename to tools/winscope-ng/src/app/components/trace_config.component.spec.ts
index 8b2cec2b4..2a53befcb 100644
--- a/tools/winscope-ng/src/app/trace_config.component.spec.ts
+++ b/tools/winscope-ng/src/app/components/trace_config.component.spec.ts
@@ -104,7 +104,7 @@ describe("TraceConfigComponent", () => {
expect(adv?.innerHTML).toContain("tracing level");
});
- it("check that changing enable config causes box to change", async () => { spyOn(component, "changeTraceCollectionConfig");
+ it("check that changing enable config causes box to change", async () => {
component.trace.config!.enableConfigs[0].enabled = false;
fixture.detectChanges();
await fixture.whenStable();
diff --git a/tools/winscope-ng/src/app/trace_config.component.ts b/tools/winscope-ng/src/app/components/trace_config.component.ts
similarity index 98%
rename from tools/winscope-ng/src/app/trace_config.component.ts
rename to tools/winscope-ng/src/app/components/trace_config.component.ts
index b329781d2..357ba5f97 100644
--- a/tools/winscope-ng/src/app/trace_config.component.ts
+++ b/tools/winscope-ng/src/app/components/trace_config.component.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Component, Input } from "@angular/core";
-import { EnableConfiguration, SelectionConfiguration, TraceConfiguration } from "../trace_collection/trace_collection_utils";
+import { EnableConfiguration, SelectionConfiguration, TraceConfiguration } from "trace_collection/trace_collection_utils";
@Component({
selector: "trace-config",
diff --git a/tools/winscope-ng/src/app/components/trace_view.component.spec.ts b/tools/winscope-ng/src/app/components/trace_view.component.spec.ts
new file mode 100644
index 000000000..c589351eb
--- /dev/null
+++ b/tools/winscope-ng/src/app/components/trace_view.component.spec.ts
@@ -0,0 +1,61 @@
+/*
+ * 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 { CommonModule } from "@angular/common";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { TraceViewComponent } from "./trace_view.component";
+import { MatCardModule } from "@angular/material/card";
+import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from "@angular/core";
+import { TraceType } from "common/trace/trace_type";
+
+describe("TraceViewComponent", () => {
+ let fixture: ComponentFixture;
+ let component: TraceViewComponent;
+ let htmlElement: HTMLElement;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ CommonModule,
+ MatCardModule
+ ],
+ declarations: [TraceViewComponent],
+ schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents();
+ fixture = TestBed.createComponent(TraceViewComponent);
+ component = fixture.componentInstance;
+ htmlElement = fixture.nativeElement;
+ component.dependencies = [TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER];
+ component.showTrace = true;
+ });
+
+ it("can be created", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("check that mat card title and contents are displayed", () => {
+ fixture.detectChanges();
+ const title = htmlElement.querySelector(".trace-card-title");
+ expect(title).toBeTruthy();
+ const header = title?.querySelector("trace-view-header");
+ expect(header).toBeTruthy();
+ });
+
+ it("check that card content is created", () => {
+ fixture.detectChanges();
+ const content = htmlElement.querySelector(".trace-card-content") as HTMLElement;
+ expect(content).toBeTruthy();
+ });
+});
diff --git a/tools/winscope-ng/src/app/components/trace_view.component.ts b/tools/winscope-ng/src/app/components/trace_view.component.ts
new file mode 100644
index 000000000..cfb56e594
--- /dev/null
+++ b/tools/winscope-ng/src/app/components/trace_view.component.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 {
+ Component,
+ Input,
+ Output,
+ EventEmitter
+} from "@angular/core";
+import { TRACE_INFO } from "../trace_info";
+import { TraceType } from "common/trace/trace_type";
+
+@Component({
+ selector: "trace-view",
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+})
+export class TraceViewComponent {
+ @Input() title!: string;
+ @Input() dependencies!: TraceType[];
+ @Input() showTrace = true;
+ @Input() cardId = 0;
+ @Output() saveTraces = new EventEmitter();
+
+ TRACE_INFO = TRACE_INFO;
+
+ onSaveTraces(dependencies: TraceType[]) {
+ this.saveTraces.emit(dependencies);
+ }
+}
diff --git a/tools/winscope-ng/src/app/components/trace_view_header.component.spec.ts b/tools/winscope-ng/src/app/components/trace_view_header.component.spec.ts
new file mode 100644
index 000000000..6a80064a1
--- /dev/null
+++ b/tools/winscope-ng/src/app/components/trace_view_header.component.spec.ts
@@ -0,0 +1,116 @@
+/*
+ * 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 { CommonModule } from "@angular/common";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { TraceViewHeaderComponent } from "./trace_view_header.component";
+import { MatIconModule } from "@angular/material/icon";
+import { MatButtonModule } from "@angular/material/button";
+import { TraceType } from "common/trace/trace_type";
+
+describe("TraceViewHeaderComponent", () => {
+ let fixture: ComponentFixture;
+ let component: TraceViewHeaderComponent;
+ let htmlElement: HTMLElement;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ CommonModule,
+ MatIconModule,
+ MatButtonModule
+ ],
+ declarations: [TraceViewHeaderComponent]
+ }).compileComponents();
+ fixture = TestBed.createComponent(TraceViewHeaderComponent);
+ component = fixture.componentInstance;
+ htmlElement = fixture.nativeElement;
+ component.dependencies = [TraceType.SURFACE_FLINGER, TraceType.WINDOW_MANAGER];
+ });
+
+ it("can be created", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("check that toggle button is displayed, expanded on default", () => {
+ component.showTrace = true;
+ fixture.detectChanges();
+ const toggleButton = htmlElement.querySelector("#toggle-btn");
+ expect(toggleButton).toBeTruthy();
+ const chevronIcon = toggleButton?.querySelector("mat-icon");
+ expect(chevronIcon).toBeTruthy;
+ expect(chevronIcon?.innerHTML).toContain("expand_more");
+ });
+
+ it("check that toggle button icon is a right chevron when minimised ", () => {
+ component.showTrace = false;
+ fixture.detectChanges();
+ const toggleButton = htmlElement.querySelector("#toggle-btn");
+ const chevronIcon = toggleButton?.querySelector("mat-icon");
+ expect(chevronIcon?.innerHTML).toContain("chevron_right");
+ });
+
+ it("check that clicking toggle button causes view to minimise", async () => {
+ component.showTrace = true;
+ fixture.detectChanges();
+ spyOn(component, "toggleView").and.callThrough();
+ const button: HTMLButtonElement | null = htmlElement.querySelector("#toggle-btn");
+ expect(button).toBeInstanceOf(HTMLButtonElement);
+ button?.dispatchEvent(new Event("click"));
+ await fixture.whenStable();
+ expect(component.toggleView).toHaveBeenCalled();
+ fixture.detectChanges();
+ expect (htmlElement.querySelector("#toggle-btn")?.querySelector("mat-icon")?.innerHTML).toContain("chevron_right");
+ });
+
+ it("check that dependency icons show", () => {
+ fixture.detectChanges();
+ const dependencyIcons = htmlElement.querySelectorAll("#dep-icon");
+ expect(dependencyIcons).toBeTruthy();
+ expect(dependencyIcons.length).toBe(2);
+ });
+
+ it("check that title is displayed", () => {
+ component.title = "Surface Flinger, Window Manager";
+ fixture.detectChanges();
+ const title = htmlElement.querySelector(".trace-card-title-text");
+ expect(title).toBeTruthy();
+ expect(title?.innerHTML).toContain("Surface Flinger");
+ expect(title?.innerHTML).toContain("Window Manager");
+ });
+
+ it("check that save button is displayed", () => {
+ fixture.detectChanges();
+ const saveButton = htmlElement.querySelectorAll("#save-btn");
+ expect(saveButton).toBeTruthy();
+ });
+
+ it("check that clicking save button emits", async () => {
+ spyOn(component, "saveTraces").and.callThrough();
+ spyOn(component.saveTraceChange, "emit");
+ const button: HTMLButtonElement | null = htmlElement.querySelector("#save-btn");
+ expect(button).toBeInstanceOf(HTMLButtonElement);
+ button?.dispatchEvent(new Event("click"));
+ await fixture.whenStable();
+ expect(component.saveTraces).toHaveBeenCalled();
+ expect(component.saveTraceChange.emit).toHaveBeenCalled();
+ });
+
+ it("check that screenshot button is displayed", () => {
+ fixture.detectChanges();
+ const screenshotButton = htmlElement.querySelectorAll("#screenshot-btn");
+ expect(screenshotButton).toBeTruthy();
+ });
+});
diff --git a/tools/winscope-ng/src/app/components/trace_view_header.component.ts b/tools/winscope-ng/src/app/components/trace_view_header.component.ts
new file mode 100644
index 000000000..a298d8507
--- /dev/null
+++ b/tools/winscope-ng/src/app/components/trace_view_header.component.ts
@@ -0,0 +1,88 @@
+/*
+ * 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 {
+ Component,
+ Input,
+ Output,
+ EventEmitter
+} from "@angular/core";
+import { TRACE_INFO } from "../trace_info";
+import { TraceType } from "common/trace/trace_type";
+import html2canvas from "html2canvas";
+
+@Component({
+ selector: "trace-view-header",
+ template: `
+
+ {{TRACE_INFO[dep].icon}}
+
+ {{title}}
+
+
+
+ `,
+ styles: [
+ ".trace-card-title {font: inherit; display: inline-block; vertical-align: middle;}",
+ ]
+})
+export class TraceViewHeaderComponent {
+ @Input() title?: string;
+ @Input() dependencies?: TraceType[];
+ @Input() showTrace = true;
+ @Input() cardId!: number ;
+
+ @Output() showTraceChange = new EventEmitter();
+ @Output() saveTraceChange = new EventEmitter();
+
+ TRACE_INFO = TRACE_INFO;
+
+ toggleView() {
+ this.showTrace = !this.showTrace;
+ this.showTraceChange.emit(this.showTrace);
+ }
+
+ public saveTraces() {
+ this.saveTraceChange.emit(this.dependencies);
+ }
+
+ public takeScreenshot() {
+ const el = document.querySelector(`#card-${this.cardId}`);
+ if (el) {
+ html2canvas((el as HTMLElement)).then((canvas) => {
+ const uri = canvas.toDataURL();
+ const filename = "Winscope-Screenshot.png";
+ const link = document.createElement("a");
+ if (typeof link.download === "string") {
+ link.href = uri;
+ link.download = filename;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ } else {
+ window.open(uri);
+ }
+ });
+ }
+ }
+}
diff --git a/tools/winscope-ng/src/app/upload_traces.component.spec.ts b/tools/winscope-ng/src/app/components/upload_traces.component.spec.ts
similarity index 96%
rename from tools/winscope-ng/src/app/upload_traces.component.spec.ts
rename to tools/winscope-ng/src/app/components/upload_traces.component.spec.ts
index d617dbd38..6322423eb 100644
--- a/tools/winscope-ng/src/app/upload_traces.component.spec.ts
+++ b/tools/winscope-ng/src/app/components/upload_traces.component.spec.ts
@@ -17,7 +17,7 @@ import {ComponentFixture, TestBed} from "@angular/core/testing";
import {UploadTracesComponent} from "./upload_traces.component";
import { MatCardModule } from "@angular/material/card";
-describe("CollectTracesComponent", () => {
+describe("UploadTracesComponent", () => {
let fixture: ComponentFixture;
let component: UploadTracesComponent;
let htmlElement: HTMLElement;
diff --git a/tools/winscope-ng/src/app/upload_traces.component.ts b/tools/winscope-ng/src/app/components/upload_traces.component.ts
similarity index 81%
rename from tools/winscope-ng/src/app/upload_traces.component.ts
rename to tools/winscope-ng/src/app/components/upload_traces.component.ts
index 7e69f1f81..b9a7ca720 100644
--- a/tools/winscope-ng/src/app/upload_traces.component.ts
+++ b/tools/winscope-ng/src/app/components/upload_traces.component.ts
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Component, Input, Inject, Output, EventEmitter, NgZone } from "@angular/core";
-import { Core } from "app/core";
-import { TRACE_ICONS } from "app/trace_icons";
+import { Component, Input, Output, EventEmitter, Inject, NgZone } from "@angular/core";
+import { TraceCoordinator } from "app/trace_coordinator";
+import { TRACE_INFO } from "app/trace_info";
import { LoadedTrace } from "app/loaded_trace";
@Component({
@@ -57,45 +57,43 @@ import { LoadedTrace } from "app/loaded_trace";
*ngIf="this.loadedTraces.length > 0"
>
- {{TRACE_ICONS[trace.type]}}
+ {{TRACE_INFO[trace.type].icon}}
{{trace.name}} ({{trace.type}})
`,
- styles: [".drop-info{font-weight: normal;}"]
+ styles: [
+ ".drop-info{font-weight: normal; pointer-events: none;}",
+ ]
})
export class UploadTracesComponent {
- @Input()
- core?: Core;
-
- @Output()
- coreChange = new EventEmitter();
+ @Input() traceCoordinator!: TraceCoordinator;
dataLoaded = false;
- @Output()
- dataLoadedChange = new EventEmitter();
-
- loadedTraces: LoadedTrace[] = [];
- TRACE_ICONS = TRACE_ICONS;
+ @Output() dataLoadedChange = new EventEmitter();
constructor(@Inject(NgZone) private ngZone: NgZone) {}
+ loadedTraces: LoadedTrace[] = [];
+ TRACE_INFO = TRACE_INFO;
+
public async onInputFile(event: Event) {
const files = this.getInputFiles(event);
await this.processFiles(files);
}
public async processFiles(files: File[]) {
- await this.core?.addTraces(files);
+ await this.traceCoordinator.addTraces(files);
this.ngZone.run(() => {
- if (this.core) this.loadedTraces = this.core.getLoadedTraces();
+ this.loadedTraces = this.traceCoordinator.getLoadedTraces();
});
}
@@ -114,7 +112,10 @@ export class UploadTracesComponent {
}
public onClearData() {
- this.core?.clearData();
+ this.traceCoordinator.clearData();
+ this.dataLoaded = false;
+ this.loadedTraces = [];
+ this.dataLoadedChange.emit(this.dataLoaded);
}
public onFileDragIn(e: DragEvent) {
@@ -136,7 +137,7 @@ export class UploadTracesComponent {
}
public onRemoveTrace(trace: LoadedTrace) {
- this.core?.removeTrace(trace.type);
+ this.traceCoordinator.removeTrace(trace.type);
this.loadedTraces = this.loadedTraces.filter(loaded => loaded.type !== trace.type);
}
}
diff --git a/tools/winscope-ng/src/app/web_adb.component.spec.ts b/tools/winscope-ng/src/app/components/web_adb.component.spec.ts
similarity index 100%
rename from tools/winscope-ng/src/app/web_adb.component.spec.ts
rename to tools/winscope-ng/src/app/components/web_adb.component.spec.ts
diff --git a/tools/winscope-ng/src/app/web_adb.component.ts b/tools/winscope-ng/src/app/components/web_adb.component.ts
similarity index 100%
rename from tools/winscope-ng/src/app/web_adb.component.ts
rename to tools/winscope-ng/src/app/components/web_adb.component.ts
diff --git a/tools/winscope-ng/src/app/loaded_trace.ts b/tools/winscope-ng/src/app/loaded_trace.ts
index 137b56727..c24cf649c 100644
--- a/tools/winscope-ng/src/app/loaded_trace.ts
+++ b/tools/winscope-ng/src/app/loaded_trace.ts
@@ -1,4 +1,4 @@
-import { TraceType } from "../common/trace/trace_type";
+import { TraceType } from "common/trace/trace_type";
export interface LoadedTrace {
name: string;
diff --git a/tools/winscope-ng/src/app/core.ts b/tools/winscope-ng/src/app/trace_coordinator.ts
similarity index 75%
rename from tools/winscope-ng/src/app/core.ts
rename to tools/winscope-ng/src/app/trace_coordinator.ts
index 30ce5b927..bd889bee5 100644
--- a/tools/winscope-ng/src/app/core.ts
+++ b/tools/winscope-ng/src/app/trace_coordinator.ts
@@ -21,8 +21,9 @@ import { setTraces } from "trace_collection/set_traces";
import { Viewer } from "viewers/viewer";
import { ViewerFactory } from "viewers/viewer_factory";
import { LoadedTrace } from "app/loaded_trace";
+import { TRACE_INFO } from "./trace_info";
-class Core {
+class TraceCoordinator {
private parsers: Parser[];
private viewers: Viewer[];
@@ -37,7 +38,6 @@ class Core {
console.log("created parsers: ", this.parsers);
}
-
removeTrace(type: TraceType) {
this.parsers = this.parsers.filter(parser => parser.getTraceType() !== type);
}
@@ -62,10 +62,19 @@ class Core {
return this.viewers.map(viewer => viewer.getView());
}
+ getViewers(): Viewer[] {
+ return this.viewers;
+ }
+
loadedTraceTypes(): TraceType[] {
return this.parsers.map(parser => parser.getTraceType());
}
+ findParser(fileType: TraceType): Parser | null {
+ const parser = this.parsers.find(parser => parser.getTraceType() === fileType);
+ return parser ?? null;
+ }
+
getTimestamps(): Timestamp[] {
for (const type of [TimestampType.REAL, TimestampType.ELAPSED]) {
const mergedTimestamps: Timestamp[] = [];
@@ -94,8 +103,9 @@ class Core {
const traceEntries: Map = new Map();
this.parsers.forEach(parser => {
- const entry = parser.getTraceEntry(timestamp);
- if (entry != undefined) {
+ const targetTimestamp = timestamp;
+ const entry = parser.getTraceEntry(targetTimestamp);
+ if (entry !== undefined) {
traceEntries.set(parser.getTraceType(), entry);
}
});
@@ -106,10 +116,31 @@ class Core {
}
clearData() {
+ this.getViews().forEach(view => view.remove());
this.parsers = [];
this.viewers = [];
setTraces.dataReady = false;
}
+
+ saveTraces(traceTypes: TraceType[]) {
+ const blobs: Blob[] = [];
+ traceTypes.forEach(type => {
+ const trace = this.findParser(type)?.getTrace();
+ if (trace) {
+ blobs.push(trace);
+ }
+ });
+ blobs.forEach((blob, idx) => {
+ const a = document.createElement("a");
+ document.body.appendChild(a);
+ const url = window.URL.createObjectURL(blob);
+ a.href = url;
+ a.download = (blob as any).name ?? `${TRACE_INFO[traceTypes[idx]].name}.pb`;
+ a.click();
+ window.URL.revokeObjectURL(url);
+ document.body.removeChild(a);
+ });
+ }
}
-export { Core };
\ No newline at end of file
+export { TraceCoordinator };
\ No newline at end of file
diff --git a/tools/winscope-ng/src/app/trace_icons.ts b/tools/winscope-ng/src/app/trace_icons.ts
index 14cec84d7..104c0ff32 100644
--- a/tools/winscope-ng/src/app/trace_icons.ts
+++ b/tools/winscope-ng/src/app/trace_icons.ts
@@ -1,4 +1,4 @@
-import { TraceType } from "../common/trace/trace_type";
+import { TraceType } from "common/trace/trace_type";
const WINDOW_MANAGER_ICON = "view_compact";
const SURFACE_FLINGER_ICON = "filter_none";
diff --git a/tools/winscope-ng/src/app/trace_info.ts b/tools/winscope-ng/src/app/trace_info.ts
new file mode 100644
index 000000000..99999d5b6
--- /dev/null
+++ b/tools/winscope-ng/src/app/trace_info.ts
@@ -0,0 +1,88 @@
+import { TraceType } from "common/trace/trace_type";
+
+const WINDOW_MANAGER_ICON = "view_compact";
+const SURFACE_FLINGER_ICON = "filter_none";
+const SCREEN_RECORDING_ICON = "videocam";
+const TRANSACTION_ICON = "timeline";
+const WAYLAND_ICON = "filter_none";
+const PROTO_LOG_ICON = "notes";
+const SYSTEM_UI_ICON = "filter_none";
+const LAUNCHER_ICON = "filter_none";
+const IME_ICON = "keyboard";
+const ACCESSIBILITY_ICON = "accessibility";
+const TAG_ICON = "details";
+const TRACE_ERROR_ICON = "warning";
+
+type traceInfoMap = {
+ [key: number]: {
+ name: string,
+ icon: string
+ };
+}
+
+export const TRACE_INFO: traceInfoMap = {
+ [TraceType.ACCESSIBILITY]: {
+ name: "Accessibility",
+ icon: ACCESSIBILITY_ICON
+ },
+ [TraceType.WINDOW_MANAGER]: {
+ name: "Window Manager",
+ icon: WINDOW_MANAGER_ICON
+ },
+ [TraceType.SURFACE_FLINGER]: {
+ name: "Surface Flinger",
+ icon: SURFACE_FLINGER_ICON
+ },
+ [TraceType.SCREEN_RECORDING]: {
+ name: "Screen Recording",
+ icon: SCREEN_RECORDING_ICON
+ },
+ [TraceType.TRANSACTIONS]: {
+ name: "Transactions",
+ icon: TRANSACTION_ICON
+ },
+ [TraceType.TRANSACTIONS_LEGACY]: {
+ name: "Transactions Legacy",
+ icon: TRANSACTION_ICON
+ },
+ [TraceType.WAYLAND]: {
+ name: "Wayland",
+ icon: WAYLAND_ICON
+ },
+ [TraceType.WAYLAND_DUMP]: {
+ name: "Wayland Dump",
+ icon: WAYLAND_ICON
+ },
+ [TraceType.PROTO_LOG]: {
+ name: "Proto Log",
+ icon: PROTO_LOG_ICON
+ },
+ [TraceType.SYSTEM_UI]: {
+ name: "System UI",
+ icon: SYSTEM_UI_ICON
+ },
+ [TraceType.LAUNCHER]: {
+ name: "Launcher",
+ icon: LAUNCHER_ICON
+ },
+ [TraceType.INPUT_METHOD_CLIENTS]: {
+ name: "IME Clients",
+ icon: IME_ICON
+ },
+ [TraceType.INPUT_METHOD_SERVICE]: {
+ name: "IME Service",
+ icon: IME_ICON
+ },
+ [TraceType.INPUT_METHOD_MANAGER_SERVICE]: {
+ name: "IME Manager Service",
+ icon: IME_ICON
+ },
+ [TraceType.TAG]: {
+ name: "Tag",
+ icon: TAG_ICON
+ },
+ [TraceType.ERROR]: {
+ name: "Error",
+ icon: TRACE_ERROR_ICON
+ },
+};
diff --git a/tools/winscope-ng/src/styles.css b/tools/winscope-ng/src/styles.css
index feaa90e9f..120f7a884 100644
--- a/tools/winscope-ng/src/styles.css
+++ b/tools/winscope-ng/src/styles.css
@@ -16,11 +16,18 @@
@import "~@angular/material/prebuilt-themes/indigo-pink.css";
@import 'https://fonts.googleapis.com/icon?family=Material+Icons';
+#app-title {
+ font-weight: bold;
+ font-family: Arial, Helvetica, sans-serif;
+ color:rgb(194, 65, 108);
+ font-size: 30;
+}
+
#title {
font-weight: bold;
font-family: Arial, Helvetica, sans-serif;
color:rgb(194, 65, 108);
- font-size: 20;
+ font-size: 24;
}
button {
@@ -29,20 +36,53 @@ button {
.homepage-card {
border: 1px solid rgb(129, 129, 129);
- width: 45rem;
- height: 30rem;
+ width: 50%;
+ height: 35rem;
overflow: auto;
display: flex;
margin: 10px;
}
-mat-checkbox {
- margin-left: 10px;
+.trace-card {
+ border: 1px solid rgb(129, 129, 129);
+ height: 100%;
+ overflow: auto;
+ display: flex;
+ margin: 10px;
}
-mat-form-field {
- margin: 10px;
- height: 5px;
+.card-grid {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+ overflow: auto;
+}
+
+mat-checkbox {
+ margin-left: 5px;
+}
+
+.mat-checkbox .mat-checkbox-frame {
+ transform: scale(0.7);
+}
+
+.mat-checkbox-checked .mat-checkbox-background {
+ transform: scale(0.7);
+}
+
+.mat-checkbox-indeterminate .mat-checkbox-background {
+ transform: scale(0.7);
+}
+
+.mat-radio-button, .mat-radio-button-frame {
+ transform: scale(0.8);
+}
+
+.mat-form-field {
+ transform: scale(0.85);
+ margin: 2px;
+ padding: 0;
}
mat-icon {
@@ -75,12 +115,12 @@ button.mat-raised-button {
}
.drop-box {
- outline: 2px dashed rgb(194, 65, 108); /* the dash box */
+ outline: 2px dashed rgb(194, 65, 108);
outline-offset: -10px;
background: white;
color: rgb(194, 65, 108);
padding: 10px 10px 10px 10px;
- min-height: 200px; /* minimum height */
+ min-height: 200px;
position: relative;
cursor: pointer;
display: flex;
@@ -104,4 +144,19 @@ button.mat-raised-button {
background-color: rgb(194, 65, 108);
border-radius: 21.5px;
cursor: pointer;
-}
\ No newline at end of file
+}
+
+.viewers.hide {
+ display: none !important;
+}
+
+[hidden] {
+ display: none !important;
+}
+
+.icon-button {
+ background: none;
+ border: none;
+ display: inline-block;
+ vertical-align: middle;
+}
diff --git a/tools/winscope-ng/src/test/e2e/viewer_surface_flinger.spec.ts b/tools/winscope-ng/src/test/e2e/viewer_surface_flinger.spec.ts
new file mode 100644
index 000000000..fb82ee53c
--- /dev/null
+++ b/tools/winscope-ng/src/test/e2e/viewer_surface_flinger.spec.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 {browser, element, by, ElementFinder} from "protractor";
+import {E2eTestUtils} from "./utils";
+
+describe("Viewer SurfaceFlinger", () => {
+ beforeAll(async () => {
+ browser.manage().timeouts().implicitlyWait(1000);
+ browser.get("file://" + E2eTestUtils.getProductionIndexHtmlPath());
+ }),
+
+ it("processes trace and renders view", () => {
+ const inputFile = element(by.css("input[type=\"file\"]"));
+ inputFile.sendKeys(E2eTestUtils.getFixturePath("traces/elapsed_and_real_timestamp/SurfaceFlinger.pb"));
+
+ const loadData = element(by.css(".load-btn"));
+ loadData.click();
+
+ const surfaceFlingerCard: ElementFinder = element(by.css(".trace-card"));
+ expect(surfaceFlingerCard.getText()).toContain("Surface Flinger");
+ });
+});
diff --git a/tools/winscope-ng/src/test/e2e/winscope.spec.ts b/tools/winscope-ng/src/test/e2e/winscope.spec.ts
index ac7032ad1..bc22e930c 100644
--- a/tools/winscope-ng/src/test/e2e/winscope.spec.ts
+++ b/tools/winscope-ng/src/test/e2e/winscope.spec.ts
@@ -22,7 +22,7 @@ describe("winscope", () => {
}),
it("has title", () => {
- const title = element(by.css("#title"));
+ const title = element(by.css("#app-title"));
expect(title.getText()).toContain("Winscope");
});
});
\ No newline at end of file
diff --git a/tools/winscope-ng/src/trace_collection/proxy_client.ts b/tools/winscope-ng/src/trace_collection/proxy_client.ts
index b10d65e7a..e3ac3ff32 100644
--- a/tools/winscope-ng/src/trace_collection/proxy_client.ts
+++ b/tools/winscope-ng/src/trace_collection/proxy_client.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { PersistentStore } from "../common/persistent_store";
+import { PersistentStore } from "common/persistent_store";
import { configMap, TRACES } from "./trace_collection_utils";
import { setTraces, SetTraces } from "./set_traces";
import { Device } from "./connection";
diff --git a/tools/winscope-ng/src/viewers/canvas_graphics.ts b/tools/winscope-ng/src/viewers/canvas_graphics.ts
new file mode 100644
index 000000000..86d24d3e5
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/canvas_graphics.ts
@@ -0,0 +1,416 @@
+/*
+ * 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 { Rectangle } from "viewers/viewer_surface_flinger/ui_data";
+import * as THREE from "three";
+import { CSS2DRenderer, CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer";
+
+export class CanvasGraphics {
+ constructor() {
+ //set up camera
+ const left = -this.cameraHalfWidth,
+ right = this.cameraHalfWidth,
+ top = this.cameraHalfHeight,
+ bottom = -this.cameraHalfHeight,
+ near = 0.001,
+ far = 100;
+ this.camera = new THREE.OrthographicCamera(
+ left,right,top,bottom,near,far
+ );
+ }
+
+ initialise(canvas: HTMLCanvasElement) {
+ // initialise canvas
+ this.canvas = canvas;
+ }
+
+ refreshCanvas() {
+ //set canvas size
+ this.canvas!.style.width = "100%";
+ this.canvas!.style.height = "40rem";
+
+ // TODO: click and drag rotation control
+ this.camera.position.set(this.xyCameraPos, this.xyCameraPos, 6);
+ this.camera.lookAt(0, 0, 0);
+ this.camera.zoom = this.camZoom;
+ this.camera.updateProjectionMatrix();
+
+ // scene
+ const scene = new THREE.Scene();
+
+ // renderers
+ const renderer = new THREE.WebGLRenderer({
+ antialias: true,
+ canvas: this.canvas,
+ alpha: true
+ });
+ let labelRenderer: CSS2DRenderer;
+ if (document.querySelector("#labels-canvas")) {
+ labelRenderer = new CSS2DRenderer({
+ element: document.querySelector("#labels-canvas")! as HTMLElement
+ });
+ } else {
+ labelRenderer = new CSS2DRenderer();
+ labelRenderer.domElement.style.position = "absolute";
+ labelRenderer.domElement.style.top = "0px";
+ labelRenderer.domElement.style.width = "100%";
+ labelRenderer.domElement.style.height = "40rem";
+ labelRenderer.domElement.id = "labels-canvas";
+ labelRenderer.domElement.style.pointerEvents = "none";
+ document.querySelector(".canvas-container")?.appendChild(labelRenderer.domElement);
+ }
+
+ // set various factors for shading and shifting
+ const visibleDarkFactor = 0, nonVisibleDarkFactor = 0, rectCounter = 0;
+ const numberOfRects = this.rects.length;
+ const numberOfVisibleRects = this.rects.filter(rect => rect.isVisible).length;
+ const numberOfDisplayRects = this.rects.filter(rect => rect.isDisplay).length;
+
+ const zShift = numberOfRects*this.layerSeparation;
+ let xShift = 0, yShift = 3.25, labelYShift = 0;
+
+ if (this.isLandscape) {
+ xShift = 1;
+ yShift = 1.5;
+ labelYShift = 1.25;
+ }
+
+ const lowestY = Math.min(...this.rects.map(rect => {
+ const y = rect.topLeft.y - rect.height + this.lowestYShift;
+ if (this.isLandscape) {
+ if (y<0) {
+ return 0;
+ } else if (y > 2) {
+ return 2;
+ }
+ } else if (y > -1) {
+ return -1;
+ }
+ return y;
+ })) - labelYShift;
+
+ this.drawScene(
+ rectCounter,
+ numberOfVisibleRects,
+ visibleDarkFactor,
+ numberOfDisplayRects,
+ nonVisibleDarkFactor,
+ numberOfRects,
+ scene,
+ xShift,
+ yShift,
+ zShift,
+ lowestY
+ );
+
+ // const axesHelper = new THREE.AxesHelper(1);
+ // const gridHelper = new THREE.GridHelper(5);
+ // scene.add(axesHelper, gridHelper)
+
+ renderer.setSize(this.canvas!.clientWidth, this.canvas!.clientHeight);
+ renderer.setPixelRatio(window.devicePixelRatio);
+ renderer.render(scene, this.camera);
+
+ labelRenderer.setSize(this.canvas!.clientWidth, this.canvas!.clientHeight);
+ labelRenderer.render(scene, this.camera);
+ }
+
+ private drawScene(
+ rectCounter: number,
+ visibleRects: number,
+ visibleDarkFactor:number,
+ displayRects: number,
+ nonVisibleDarkFactor: number,
+ numberOfRects: number,
+ scene: THREE.Scene,
+ xShift: number,
+ yShift: number,
+ zShift: number,
+ lowestY: number
+ ) {
+ this.targetObjects = [];
+ this.rects.forEach(rect => {
+ const visibleViewInvisibleRect = this.visibleView && !rect.isVisible;
+ const xrayViewNoVirtualDisplaysVirtualRect = !this.visibleView && !this.showVirtualDisplays && rect.isDisplay && rect.isVirtual;
+ if (visibleViewInvisibleRect || xrayViewNoVirtualDisplaysVirtualRect) {
+ rectCounter++;
+ return;
+ }
+
+ //set colour mapping
+ let planeColor;
+ if (this.highlighted === `${rect.id}`) {
+ planeColor = this.colorMapping("highlight", numberOfRects, 0);
+ } else if (rect.isVisible) {
+ planeColor = this.colorMapping("green", visibleRects, visibleDarkFactor);
+ visibleDarkFactor++;
+ } else if (rect.isDisplay) {
+ planeColor = this.colorMapping("grey", displayRects, nonVisibleDarkFactor);
+ nonVisibleDarkFactor++;
+ } else {
+ planeColor = this.colorMapping("unknown", numberOfRects, 0);
+ }
+
+ //set plane geometry and material
+ const geometry = new THREE.PlaneGeometry(rect.width, rect.height);
+ const planeRect = this.setPlaneMaterial(rect, geometry, planeColor, xShift, yShift, zShift);
+ scene.add(planeRect);
+ zShift -= this.layerSeparation;
+
+ // bolder edges of each plane if in x-ray view
+ if (!this.visibleView) {
+ const edgeSegments = this.setEdgeMaterial(planeRect, geometry);
+ scene.add(edgeSegments);
+ }
+
+ // label circular marker
+ const circle = this.setCircleMaterial(planeRect, rect);
+ scene.add(circle);
+ this.targetObjects.push(planeRect);
+
+ // label line
+ const [line, rectLabel] = this.createLabel(rect, circle, lowestY, rectCounter);
+ scene.add(line);
+ scene.add(rectLabel);
+ rectCounter++;
+ });
+ }
+
+ private setPlaneMaterial(
+ rect: Rectangle,
+ geometry: THREE.PlaneGeometry,
+ color: THREE.Color,
+ xShift: number,
+ yShift: number,
+ zShift: number
+ ) {
+ const planeRect = new THREE.Mesh(
+ geometry,
+ new THREE.MeshBasicMaterial({
+ color: color,
+ opacity: this.visibleView ? 1 : 0.75,
+ transparent: true,
+ }));
+ planeRect.position.y = rect.topLeft.y - rect.height/2 + yShift;
+ planeRect.position.x = rect.topLeft.x + rect.width/2 - xShift;
+ planeRect.position.z = zShift;
+ planeRect.name = `${rect.id}`;
+ return planeRect;
+ }
+
+ private setEdgeMaterial(planeRect: THREE.Mesh, geometry: THREE.PlaneGeometry) {
+ const edgeColor = 0x000000;
+ const edgeGeo = new THREE.EdgesGeometry(geometry);
+ const edgeMaterial = new THREE.LineBasicMaterial({color: edgeColor, linewidth: 1});
+ const edgeSegments = new THREE.LineSegments(
+ edgeGeo, edgeMaterial
+ );
+ edgeSegments.position.set(planeRect.position.x, planeRect.position.y, planeRect.position.z);
+ return edgeSegments;
+ }
+
+ private setCircleMaterial(planeRect: THREE.Mesh, rect: Rectangle) {
+ const labelCircle = new THREE.CircleGeometry(0.02, 200);
+ const circleMaterial = new THREE.MeshBasicMaterial({ color: 0x000000 });
+ const circle = new THREE.Mesh(labelCircle, circleMaterial);
+ circle.position.set(
+ planeRect.position.x + rect.width/2 - 0.05,
+ planeRect.position.y,
+ planeRect.position.z + 0.05
+ );
+ circle.rotateY(THREE.MathUtils.degToRad(30));
+ return circle;
+ }
+
+ private createLabel(rect: Rectangle, circle: THREE.Mesh, lowestY: number, rectCounter: number):
+ [THREE.Line, CSS2DObject] {
+ const labelText = this.shortenText(rect.label);
+ const isGrey = !this.visibleView && !rect.isVisible;
+ let cornerPos, endPos;
+ const labelYSeparation = 0.3;
+ if (this.isLandscape) {
+ cornerPos = new THREE.Vector3(
+ circle.position.x, lowestY - 0.5 - rectCounter*labelYSeparation, circle.position.z
+ );
+ } else {
+ cornerPos = new THREE.Vector3(
+ circle.position.x, lowestY + 0.5 - rectCounter*labelYSeparation, circle.position.z
+ );
+ }
+
+ const linePoints = [circle.position, cornerPos];
+ if (this.isLandscape && cornerPos.x > 0 || !this.isLandscape) {
+ endPos = new THREE.Vector3(cornerPos.x - 1, cornerPos.y - this.labelShift, cornerPos.z);
+ } else {
+ endPos = cornerPos;
+ }
+ linePoints.push(endPos);
+
+ //add rectangle label
+ document.querySelector(`.label-${rectCounter}`)?.remove();
+ const rectLabelDiv: HTMLElement = document.createElement("div");
+ this.labelElements.push(rectLabelDiv);
+ rectLabelDiv.className = `label-${rectCounter}`;
+ rectLabelDiv.textContent = labelText;
+ rectLabelDiv.style.fontSize = "10px";
+ if (isGrey) {
+ rectLabelDiv.style.color = "grey";
+ }
+ const rectLabel = new CSS2DObject(rectLabelDiv);
+ rectLabel.name = rect.label;
+
+ const textCanvas = document.createElement("canvas");
+ const labelContext = textCanvas.getContext("2d");
+ let labelWidth = 0;
+ if (labelContext?.font) {
+ labelContext.font = rectLabelDiv.style.font;
+ labelWidth = labelContext?.measureText(labelText).width;
+ }
+ textCanvas.remove();
+
+ if (this.isLandscape && endPos.x < 0) {
+ rectLabel.position.set(
+ endPos.x + 0.6, endPos.y - 0.15, endPos.z - 0.6
+ );
+ } else {
+ rectLabel.position.set(
+ endPos.x - labelWidth * this.labelXFactor, endPos.y - this.labelShift * labelWidth * this.labelXFactor, endPos.z
+ );
+ }
+
+ const lineGeo = new THREE.BufferGeometry().setFromPoints(linePoints);
+ const lineMaterial = new THREE.LineBasicMaterial({color: isGrey ? 0x808080 : 0x000000});
+ const line = new THREE.Line(lineGeo, lineMaterial);
+
+ return [line, rectLabel];
+ }
+
+ getCamera() {
+ return this.camera;
+ }
+
+ getTargetObjects() {
+ return this.targetObjects;
+ }
+
+ getLayerSeparation() {
+ return this.layerSeparation;
+ }
+
+ getVisibleView() {
+ return this.visibleView;
+ }
+
+ getXyCameraPos() {
+ return this.xyCameraPos;
+ }
+
+ getShowVirtualDisplays() {
+ return this.showVirtualDisplays;
+ }
+
+ updateLayerSeparation(userInput: number) {
+ this.layerSeparation = userInput;
+ }
+
+ updateRotation(userInput: number) {
+ this.xyCameraPos = userInput;
+ this.camZoom = userInput/4 * 0.2 + 0.9;
+ this.labelShift = userInput/4 * this.maxLabelShift;
+ this.lowestYShift = userInput/4 + 2;
+ }
+
+ updateHighlighted(highlighted: string) {
+ this.highlighted = highlighted;
+ }
+
+ updateRects(rects: Rectangle[]) {
+ this.rects = rects;
+ }
+
+ updateIsLandscape(isLandscape: boolean) {
+ this.isLandscape = isLandscape;
+ }
+
+ updateVisibleView(visible: boolean) {
+ this.visibleView = visible;
+ }
+
+ updateVirtualDisplays(show: boolean) {
+ this.showVirtualDisplays = show;
+ }
+
+ clearLabelElements() {
+ this.labelElements.forEach(el => el.remove());
+ }
+
+ updateZoom(isZoomIn: boolean) {
+ if (isZoomIn && this.camZoom < 2) {
+ this.camZoom += this.camZoomFactor * 1.5;
+ } else if (!isZoomIn && this.camZoom > 0.5) {
+ this.camZoom -= this.camZoomFactor * 1.5;
+ }
+ }
+
+ colorMapping(scale: string, numberOfRects: number, darkFactor:number): THREE.Color {
+ if (scale === "highlight") {
+ return new THREE.Color(0xD2E3FC);
+ } else if (scale === "grey") {
+ // darkness of grey rect depends on z order - darkest 64, lightest 128
+ //Separate RGB values between 0 and 1
+ const lower = 120;
+ const upper = 220;
+ const darkness = ((upper-lower)*(numberOfRects-darkFactor)/numberOfRects + lower)/255;
+ return new THREE.Color(darkness, darkness, darkness);
+ } else if (scale === "green") {
+ // darkness of green rect depends on z order
+ //Separate RGB values between 0 and 1
+ const red = ((200-45)*(numberOfRects-darkFactor)/numberOfRects + 45)/255;
+ const green = ((232-182)*(numberOfRects-darkFactor)/numberOfRects + 182)/255;
+ const blue = ((183-44)*(numberOfRects-darkFactor)/numberOfRects + 44)/255;
+ return new THREE.Color(red, green, blue);
+ } else {
+ return new THREE.Color(0, 0, 0);
+ }
+ }
+
+ shortenText(text: string): string {
+ if (text.length > 40) {
+ text = text.slice(0, 40);
+ }
+ return text;
+ }
+
+ // dynamic scaling and canvas variables
+ readonly cameraHalfWidth = 2.8;
+ readonly cameraHalfHeight = 3.2;
+ private readonly maxLabelShift = 0.305;
+ private readonly labelXFactor = 0.008;
+ private lowestYShift = 3;
+ private camZoom = 1.1;
+ private camZoomFactor = 0.1;
+ private labelShift = this.maxLabelShift;
+ private highlighted = "";
+ private visibleView = false;
+ private isLandscape = false;
+ private showVirtualDisplays = false;
+ private layerSeparation = 0.4;
+ private xyCameraPos = 4;
+ private camera: THREE.OrthographicCamera;
+ private rects: Rectangle[] = [];
+ private labelElements: HTMLElement[] = [];
+ private targetObjects: any[] = [];
+ private canvas?: HTMLCanvasElement;
+}
diff --git a/tools/winscope-ng/src/viewers/hierarchy.component.ts b/tools/winscope-ng/src/viewers/hierarchy.component.ts
new file mode 100644
index 000000000..386a1dad0
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/hierarchy.component.ts
@@ -0,0 +1,29 @@
+/*
+ * 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 { Component } from "@angular/core";
+
+@Component({
+ selector: "hierarchy-view",
+ template: `
+ Hierarchy
+ `,
+ styles: [
+ ".trace-view-subtitle { font-size: 18px}"
+ ]
+})
+
+export class HierarchyComponent {
+}
diff --git a/tools/winscope-ng/src/viewers/properties.component.ts b/tools/winscope-ng/src/viewers/properties.component.ts
new file mode 100644
index 000000000..92fc1ff1c
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/properties.component.ts
@@ -0,0 +1,26 @@
+/*
+ * 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 { Component } from "@angular/core";
+
+@Component({
+ selector: "properties-view",
+ template: `
+ Properties
+ `,
+})
+
+export class PropertiesComponent {
+}
diff --git a/tools/winscope-ng/src/viewers/rects.component.spec.ts b/tools/winscope-ng/src/viewers/rects.component.spec.ts
new file mode 100644
index 000000000..1643e889d
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/rects.component.spec.ts
@@ -0,0 +1,121 @@
+/*
+ * 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 { CommonModule } from "@angular/common";
+import { Component , ViewChild } from "@angular/core";
+import { ComponentFixture, TestBed } from "@angular/core/testing";
+import { RectsComponent } from "./rects.component";
+import { MatCheckboxModule } from "@angular/material/checkbox";
+import { MatCardModule } from "@angular/material/card";
+import { MatRadioModule } from "@angular/material/radio";
+import { MatSliderModule } from "@angular/material/slider";
+import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
+import { Rectangle } from "./viewer_surface_flinger/ui_data";
+
+describe("RectsComponent", () => {
+ let component: TestHostComponent;
+ let fixture: ComponentFixture;
+ let htmlElement: HTMLElement;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ CommonModule,
+ MatCheckboxModule,
+ MatCardModule,
+ MatSliderModule,
+ MatRadioModule
+ ],
+ declarations: [RectsComponent, TestHostComponent],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TestHostComponent);
+ component = fixture.componentInstance;
+ htmlElement = fixture.nativeElement;
+ fixture.detectChanges();
+ });
+
+ it("can be created", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("check that layer separation slider is rendered", () => {
+ fixture.detectChanges();
+ const slider = htmlElement.querySelector("mat-slider");
+ expect(slider).toBeTruthy();
+ });
+
+ it("check that layer separation slider causes view to change", () => {
+ const slider = htmlElement.querySelector("mat-slider");
+ spyOn(component.rectsComponent.canvasGraphics, "updateLayerSeparation");
+ slider?.dispatchEvent(new MouseEvent("mousedown"));
+ fixture.detectChanges();
+ expect(component.rectsComponent.canvasGraphics.updateLayerSeparation).toHaveBeenCalled();
+ });
+
+ it("check that rects canvas is rendered", () => {
+ fixture.detectChanges();
+ const rectsCanvas = htmlElement.querySelector("#rects-canvas");
+ expect(rectsCanvas).toBeTruthy();
+ });
+
+ it("check that canvas is refreshed if rects are present", async () => {
+ component.addRects([
+ {
+ topLeft: {x:0, y:0},
+ bottomRight: {x:1, y:-1},
+ label: "rectangle1",
+ transform: {
+ matrix: {
+ dsdx: 1,
+ dsdy: 0,
+ dtdx: 0,
+ dtdy: 1,
+ tx: 0,
+ ty: 0
+ }
+ },
+ height: 1,
+ width: 1,
+ isVisible: true,
+ isDisplay: false,
+ ref: null,
+ id: 12345,
+ stackId: 0,
+ }
+ ]);
+ spyOn(component.rectsComponent, "drawRects").and.callThrough();
+ fixture.detectChanges();
+ expect(component.rectsComponent.drawRects).toHaveBeenCalled();
+ });
+
+ @Component({
+ selector: "host-component",
+ template: ""
+ })
+ class TestHostComponent {
+ public rects: Rectangle[] = [];
+
+ addRects(newRects: Rectangle[]) {
+ this.rects = newRects;
+ }
+
+ @ViewChild(RectsComponent)
+ public rectsComponent!: RectsComponent;
+ }
+});
diff --git a/tools/winscope-ng/src/viewers/rects.component.ts b/tools/winscope-ng/src/viewers/rects.component.ts
new file mode 100644
index 000000000..b8a10e97a
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/rects.component.ts
@@ -0,0 +1,286 @@
+/*
+ * 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 { Component, Input, OnChanges, OnDestroy, Inject, ElementRef, SimpleChanges } from "@angular/core";
+import { RectsUtils } from "./rects_utils";
+import { Point, Rectangle, RectMatrix, RectTransform } from "viewers/viewer_surface_flinger/ui_data";
+import { interval, Subscription } from "rxjs";
+import { CanvasGraphics } from "./canvas_graphics";
+import * as THREE from "three";
+
+@Component({
+ selector: "rects-view",
+ template: `
+
+
+ Visible
+ X-ray
+
+
+
+ Show virtual displays
+
+
+
+
+
+
+
+
+
+
+ `,
+ styles: [
+ "@import 'https://fonts.googleapis.com/icon?family=Material+Icons';",
+ ".rects-content {position: relative}",
+ ".canvas-container {height: 40rem; width: 100%; position: relative}",
+ "#rects-canvas {height: 40rem; width: 100%; cursor: pointer; position: absolute; top: 0px}",
+ "#labels-canvas {height: 40rem; width: 100%; position: absolute; top: 0px}",
+ ".view-controls {display: inline-block; position: relative; min-height: 72px}",
+ ".zoom-container {position: absolute; top: 0px; z-index: 10}",
+ "#zoom-btn {position:relative; display: block; background: none; border: none}",
+ "mat-radio-button {font-size: 16px; font-weight: normal}",
+ ".mat-radio-button, .mat-radio-button-frame {transform: scale(0.8);}",
+ ".rects-checkbox {font-size: 14px; font-weight: normal}",
+ "mat-icon {margin: 5px}",
+ "mat-checkbox {margin-left: 5px;}",
+ ".mat-checkbox .mat-checkbox-frame { transform: scale(0.7);}",
+ ".mat-checkbox-checked .mat-checkbox-background {transform: scale(0.7);}",
+ ".mat-checkbox-indeterminate .mat-checkbox-background {transform: scale(0.7);}",
+ ]
+})
+
+export class RectsComponent implements OnChanges, OnDestroy {
+ @Input() rects!: Rectangle[];
+
+ @Input() highlighted = "";
+
+ constructor(
+ @Inject(ElementRef) private elementRef: ElementRef,
+ ) {
+ this.canvasGraphics = new CanvasGraphics();
+ }
+
+ ngOnDestroy() {
+ if (this.canvasSubscription) {
+ this.canvasSubscription.unsubscribe();
+ }
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (this.rects.length > 0) {
+ //change in rects so they must undergo transformation and scaling before canvas refreshed
+ this.canvasGraphics.clearLabelElements();
+ this.rects = this.rects.filter(rect => rect.isVisible || rect.isDisplay);
+ this.displayRects = this.rects.filter(rect => rect.isDisplay);
+ this.computeBounds();
+ this.rects = this.rects.map(rect => {
+ if (changes["rects"] && rect.transform) {
+ return RectsUtils.transformRect(rect.transform.matrix ?? rect.transform, rect);
+ } else {
+ return rect;
+ }
+ });
+ this.scaleRects();
+ this.drawRects();
+ } else if (this.canvasSubscription) {
+ this.canvasSubscription.unsubscribe();
+ }
+ }
+
+ onRectClick(event:PointerEvent) {
+ this.setNormalisedMousePos(event);
+ const raycaster = new THREE.Raycaster();
+ raycaster.setFromCamera(this.mouse, this.canvasGraphics.getCamera());
+ // create an array containing all objects in the scene with which the ray intersects
+ const intersects = raycaster.intersectObjects(this.canvasGraphics.getTargetObjects());
+ // if there is one (or more) intersections
+ if (intersects.length > 0){
+ if (this.highlighted === intersects[0].object.name) {
+ this.highlighted = "";
+ this.canvasGraphics.updateHighlighted("");
+ } else {
+ this.highlighted = intersects[0].object.name;
+ this.canvasGraphics.updateHighlighted(intersects[0].object.name);
+ }
+ this.updateHighlightedRect();
+ }
+ }
+
+ setNormalisedMousePos(event:PointerEvent) {
+ event.preventDefault();
+ const canvas = (event.target as Element);
+ const canvasOffset = canvas.getBoundingClientRect();
+ this.mouse.x = ((event.clientX-canvasOffset.left)/canvas.clientWidth) * 2 - 1;
+ this.mouse.y = -((event.clientY-canvasOffset.top)/canvas.clientHeight) * 2 + 1;
+ this.mouse.z = 0;
+ }
+
+ updateHighlightedRect() {
+ const event: CustomEvent = new CustomEvent("highlightedChange", {
+ bubbles: true,
+ detail: { layerId: this.highlighted }
+ });
+ this.elementRef.nativeElement.dispatchEvent(event);
+ }
+
+ drawRects() {
+ if (this.canvasSubscription) {
+ this.canvasSubscription.unsubscribe();
+ }
+ const canvas = document.getElementById("rects-canvas") as HTMLCanvasElement;
+ this.canvasGraphics.initialise(canvas);
+ this.canvasSubscription = this.drawRectsInterval.subscribe(() => {
+ this.updateVariablesBeforeRefresh();
+ this.canvasGraphics.refreshCanvas();
+ });
+ }
+
+ updateVariablesBeforeRefresh() {
+ this.canvasGraphics.updateRects(this.rects);
+ const biggestX = Math.max(...this.rects.map(rect => rect.topLeft.x + rect.width/2));
+ this.canvasGraphics.updateIsLandscape(biggestX > this.s({x: this.boundsWidth, y:this.boundsHeight}).x/2);
+ }
+
+ onChangeView(visible: boolean) {
+ this.canvasGraphics.updateVisibleView(visible);
+ this.canvasGraphics.clearLabelElements();
+ }
+
+ scaleRects() {
+ this.rects = this.rects.map(rect => {
+ rect.bottomRight = this.s(rect.bottomRight);
+ rect.topLeft = this.s(rect.topLeft);
+ rect.height = Math.abs(rect.topLeft.y - rect.bottomRight.y);
+ rect.width = Math.abs(rect.bottomRight.x - rect.topLeft.x);
+ const mat = this.getMatrix(rect);
+ if (mat) {
+ const newTranslation = this.s({x: mat.tx!, y: mat.ty!});
+ mat.tx = newTranslation.x;
+ mat.ty = newTranslation.y;
+ }
+ return rect;
+ });
+ }
+
+ computeBounds(): any {
+ this.boundsWidth = Math.max(...this.rects.map((rect) => {
+ const mat = this.getMatrix(rect);
+ if (mat) {
+ return RectsUtils.transformRect(mat, rect).width;
+ } else {
+ return rect.width;
+ }}));
+ this.boundsHeight = Math.max(...this.rects.map((rect) => {
+ const mat = this.getMatrix(rect);
+ if (mat) {
+ return RectsUtils.transformRect(mat, rect).height;
+ } else {
+ return rect.height;
+ }}));
+
+ if (this.displayRects.length > 0) {
+ this.boundsWidth = Math.min(this.boundsWidth, this.maxWidth());
+ this.boundsHeight = Math.min(this.boundsHeight, this.maxHeight());
+ }
+ }
+
+ maxWidth() {
+ return Math.max(...this.displayRects.map(rect => rect.width)) * 1.2;
+ }
+
+ maxHeight() {
+ return Math.max(...this.displayRects.map(rect => rect.height)) * 1.2;
+ }
+
+ // scales coordinates to canvas
+ s(sourceCoordinates: Point) {
+ let scale;
+ if (this.boundsWidth < this.boundsHeight) {
+ scale = this.canvasGraphics.cameraHalfHeight*2 * 0.6 / this.boundsHeight;
+ } else {
+ scale = this.canvasGraphics.cameraHalfWidth*2 * 0.6 / this.boundsWidth;
+ }
+ return {
+ x: sourceCoordinates.x * scale,
+ y: sourceCoordinates.y * scale,
+ };
+ }
+
+ getMatrix(rect: Rectangle) {
+ if (rect.transform) {
+ let matrix: RectTransform | RectMatrix = rect.transform;
+ if (rect.transform && rect.transform.matrix) {
+ matrix = rect.transform.matrix;
+ }
+ return matrix;
+ } else {
+ return false;
+ }
+ }
+
+ visibleView() {
+ return this.canvasGraphics.getVisibleView();
+ }
+
+ getLayerSeparation() {
+ return this.canvasGraphics.getLayerSeparation();
+ }
+
+ xyCameraPos() {
+ return this.canvasGraphics.getXyCameraPos();
+ }
+
+ showVirtualDisplays() {
+ return this.canvasGraphics.getShowVirtualDisplays();
+ }
+
+ canvasGraphics: CanvasGraphics;
+ private readonly _60fpsInterval = 16.66666666666667;
+ private drawRectsInterval = interval(this._60fpsInterval);
+ private boundsWidth = 0;
+ private boundsHeight = 0;
+ private displayRects!: Rectangle[];
+ private canvasSubscription?: Subscription;
+ private mouse = new THREE.Vector3(0, 0, 0);
+}
diff --git a/tools/winscope-ng/src/viewers/rects_utils.spec.ts b/tools/winscope-ng/src/viewers/rects_utils.spec.ts
new file mode 100644
index 000000000..310b4f480
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/rects_utils.spec.ts
@@ -0,0 +1,59 @@
+/*
+ * 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 { RectsUtils } from "./rects_utils";
+
+describe("RectsUtils", () => {
+ it("transforms rect", () => {
+ const transform = {
+ matrix: {
+ dsdx: 1,
+ dsdy: 0,
+ dtdx: 0,
+ dtdy: 1,
+ tx: 1,
+ ty: 1
+ }
+ };
+ const rect = {
+ topLeft: {x: 0, y: 0},
+ bottomRight: {x: 1, y: -1},
+ label: "TestRect",
+ transform: transform,
+ isVisible: true,
+ isDisplay: false,
+ height: 1,
+ width: 1,
+ ref: null,
+ id: 12345,
+ stackId: 0
+ };
+ const expected = {
+ topLeft: {x: 1, y: 1},
+ bottomRight: {x: 2, y: 0},
+ label: "TestRect",
+ transform: transform,
+ isVisible: true,
+ isDisplay: false,
+ height: 1,
+ width: 1,
+ ref: null,
+ id: 12345,
+ stackId: 0,
+ isVirtual: undefined
+ };
+ expect(RectsUtils.transformRect(rect.transform.matrix, rect)).toEqual(expected);
+ });
+});
diff --git a/tools/winscope-ng/src/viewers/rects_utils.ts b/tools/winscope-ng/src/viewers/rects_utils.ts
new file mode 100644
index 000000000..7306ab4a6
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/rects_utils.ts
@@ -0,0 +1,60 @@
+/*
+ * 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 { Point, Rectangle, RectMatrix, RectTransform } from "viewers/viewer_surface_flinger/ui_data";
+
+export const RectsUtils = {
+ multiplyMatrix(matrix:any, corner: Point): Point {
+ if (!matrix) return corner;
+ // |dsdx dsdy tx| | x | |x*dsdx + y*dsdy + tx|
+ // |dtdx dtdy ty| x | y | = |x*dtdx + y*dtdy + ty|
+ // |0 0 1 | | 1 | | 1 |
+ return {
+ x: matrix.dsdx * corner.x + matrix.dsdy * corner.y + matrix.tx,
+ y: matrix.dtdx * corner.x + matrix.dtdy * corner.y + matrix.ty,
+ };
+ },
+
+ transformRect(matrix: RectMatrix | RectTransform, rect:Rectangle): Rectangle {
+ // | dsdx dsdy tx | | left top 1 |
+ // matrix = | dtdx dtdy ty | rect = | 1 1 1 |
+ // | 0 0 1 | | 1 right bottom |
+ const tl = this.multiplyMatrix(matrix, rect.topLeft);
+ const tr = this.multiplyMatrix(matrix, {x:rect.bottomRight.x, y:rect.topLeft.y});
+ const bl = this.multiplyMatrix(matrix, {x:rect.topLeft.x, y:rect.bottomRight.y});
+ const br = this.multiplyMatrix(matrix, rect.bottomRight);
+
+ const left = Math.min(tl.x, tr.x, bl.x, br.x);
+ const top = Math.max(tl.y, tr.y, bl.y, br.y);
+ const right = Math.max(tl.x, tr.x, bl.x, br.x);
+ const bottom = Math.min(tl.y, tr.y, bl.y, br.y);
+
+ const outrect: Rectangle = {
+ topLeft: {x: left, y: top},
+ bottomRight: {x: right, y: bottom},
+ label: rect.label,
+ transform: rect.transform,
+ isVisible: rect.isVisible,
+ isDisplay: rect.isDisplay,
+ height: Math.abs(top - bottom),
+ width: Math.abs(right - left),
+ ref: rect.ref,
+ id: rect.id,
+ stackId: rect.stackId,
+ isVirtual: rect.isVirtual
+ };
+ return outrect;
+ }
+};
diff --git a/tools/winscope-ng/src/viewers/viewer.ts b/tools/winscope-ng/src/viewers/viewer.ts
index 5c66d09b2..5ad1857e0 100644
--- a/tools/winscope-ng/src/viewers/viewer.ts
+++ b/tools/winscope-ng/src/viewers/viewer.ts
@@ -19,6 +19,8 @@ interface Viewer {
//TODO: add TraceEntry data type
notifyCurrentTraceEntries(entries: Map): void;
getView(): HTMLElement;
+ getTitle(): string;
+ getDependencies(): TraceType[];
}
export { Viewer };
diff --git a/tools/winscope-ng/src/viewers/viewer_factory.ts b/tools/winscope-ng/src/viewers/viewer_factory.ts
index 82cf1e3c9..db9377d5d 100644
--- a/tools/winscope-ng/src/viewers/viewer_factory.ts
+++ b/tools/winscope-ng/src/viewers/viewer_factory.ts
@@ -16,10 +16,12 @@
import { TraceType } from "common/trace/trace_type";
import { Viewer } from "./viewer";
import { ViewerWindowManager } from "./viewer_window_manager/viewer_window_manager";
+import { ViewerSurfaceFlinger } from "./viewer_surface_flinger/viewer_surface_flinger";
class ViewerFactory {
static readonly VIEWERS = [
ViewerWindowManager,
+ ViewerSurfaceFlinger
];
public createViewers(activeTraceTypes: Set): Viewer[] {
@@ -29,7 +31,6 @@ class ViewerFactory {
const areViewerDepsSatisfied = Viewer.DEPENDENCIES.every((traceType: TraceType) =>
activeTraceTypes.has(traceType)
);
-
if (areViewerDepsSatisfied) {
viewers.push(new Viewer());
}
diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts
new file mode 100644
index 000000000..ff5c0c264
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts
@@ -0,0 +1,108 @@
+/*
+ * 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 { Rectangle, RectMatrix, RectTransform, UiData } from "viewers/viewer_surface_flinger/ui_data";
+import { TraceType } from "common/trace/trace_type";
+
+type NotifyViewCallbackType = (uiData: UiData) => void;
+
+class Presenter {
+ constructor(notifyViewCallback: NotifyViewCallbackType) {
+ this.notifyViewCallback = notifyViewCallback;
+ this.uiData = new UiData("Initial UI data");
+ this.notifyViewCallback(this.uiData);
+ }
+
+ updateHighlightedRect(event: CustomEvent) {
+ this.highlighted = event.detail.layerId;
+ this.uiData.highlighted = this.highlighted;
+ console.log("changed highlighted rect: ", this.uiData.highlighted);
+ this.notifyViewCallback(this.uiData);
+ }
+
+ notifyCurrentTraceEntries(entries: Map) {
+ const entry = entries.get(TraceType.SURFACE_FLINGER);
+ this.uiData = new UiData("New surface flinger ui data");
+ const displayRects = entry.displays.map((display: any) => {
+ const rect = display.layerStackSpace;
+ rect.label = display.name;
+ rect.id = display.id;
+ rect.stackId = display.layerStackId;
+ rect.isDisplay = true;
+ rect.isVirtual = display.isVirtual;
+ return rect;
+ }) ?? [];
+ this.uiData.highlighted = this.highlighted;
+ this.uiData.rects = this.rectsToUiData(entry.rects.concat(displayRects));
+ this.notifyViewCallback(this.uiData);
+ }
+
+ rectsToUiData(rects: any[]): Rectangle[] {
+ const uiRects: Rectangle[] = [];
+ rects.forEach((rect: any) => {
+ let t = null;
+ if (rect.transform && rect.transform.matrix) {
+ t = rect.transform.matrix;
+ } else if (rect.transform) {
+ t = rect.transform;
+ }
+ let transform: RectTransform | null = null;
+ if (t !== null) {
+ const matrix: RectMatrix = {
+ dsdx: t.dsdx,
+ dsdy: t.dsdy,
+ dtdx: t.dtdx,
+ dtdy: t.dtdy,
+ tx: t.tx,
+ ty: -t.ty
+ };
+ transform = {
+ matrix: matrix,
+ };
+ }
+
+ let isVisible = false, isDisplay = false;
+ if (rect.ref && rect.ref.isVisible) {
+ isVisible = rect.ref.isVisible;
+ }
+ if (rect.isDisplay) {
+ isDisplay = rect.isDisplay;
+ }
+
+ const newRect: Rectangle = {
+ topLeft: {x: rect.left, y: rect.top},
+ bottomRight: {x: rect.right, y: -rect.bottom},
+ height: rect.height,
+ width: rect.width,
+ label: rect.label,
+ transform: transform,
+ isVisible: isVisible,
+ isDisplay: isDisplay,
+ ref: rect.ref,
+ id: rect.id ?? rect.ref.id,
+ stackId: rect.stackId ?? rect.ref.stackId,
+ isVirtual: rect.isVirtual
+ };
+ uiRects.push(newRect);
+ });
+ return uiRects;
+ }
+
+ private readonly notifyViewCallback: NotifyViewCallbackType;
+ private uiData: UiData;
+ private highlighted = "";
+}
+
+export {Presenter};
diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/ui_data.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/ui_data.ts
new file mode 100644
index 000000000..ec1796c01
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/ui_data.ts
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+class UiData {
+ constructor(public text: string) {
+ console.log(text);
+ }
+ rects?: Rectangle[] = [];
+ highlighted?: string = "";
+}
+
+export interface Rectangle {
+ topLeft: Point;
+ bottomRight: Point;
+ label: string;
+ transform: RectTransform | null;
+ height: number;
+ width: number;
+ isVisible: boolean;
+ isDisplay: boolean;
+ ref: any;
+ id: number;
+ stackId: number;
+ isVirtual?: boolean;
+}
+
+export interface Point {
+ x: number,
+ y: number
+}
+
+export interface RectTransform {
+ matrix?: RectMatrix;
+ dsdx?: number;
+ dsdy?: number;
+ dtdx?: number;
+ dtdy?: number;
+ tx?: number;
+ ty?: number;
+}
+
+export interface RectMatrix {
+ dsdx: number;
+ dsdy: number;
+ dtdx: number;
+ dtdy: number;
+ tx: number;
+ ty: number;
+}
+
+export {UiData};
diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.spec.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.spec.ts
new file mode 100644
index 000000000..f4aa30284
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.spec.ts
@@ -0,0 +1,75 @@
+/*
+ * 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 {ComponentFixture, TestBed} from "@angular/core/testing";
+import {ViewerSurfaceFlingerComponent} from "./viewer_surface_flinger.component";
+
+import { HierarchyComponent } from "viewers/hierarchy.component";
+import { PropertiesComponent } from "viewers/properties.component";
+import { RectsComponent } from "viewers/rects.component";
+import { MatIconModule } from "@angular/material/icon";
+import { MatCardModule } from "@angular/material/card";
+import { ComponentFixtureAutoDetect } from "@angular/core/testing";
+import { CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
+
+describe("ViewerSurfaceFlingerComponent", () => {
+ let fixture: ComponentFixture;
+ let component: ViewerSurfaceFlingerComponent;
+ let htmlElement: HTMLElement;
+
+ beforeAll(async () => {
+ await TestBed.configureTestingModule({
+ providers: [
+ { provide: ComponentFixtureAutoDetect, useValue: true }
+ ],
+ imports: [
+ MatIconModule,
+ MatCardModule
+ ],
+ declarations: [
+ ViewerSurfaceFlingerComponent,
+ HierarchyComponent,
+ PropertiesComponent,
+ RectsComponent
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ViewerSurfaceFlingerComponent);
+ component = fixture.componentInstance;
+ htmlElement = fixture.nativeElement;
+ });
+
+ it("can be created", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("creates rects view", () => {
+ const rectsView = htmlElement.querySelector(".rects-view");
+ expect(rectsView).toBeTruthy();
+ });
+
+ it("creates hierarchy view", () => {
+ const hierarchyView = htmlElement.querySelector("#sf-hierarchy-view");
+ expect(hierarchyView).toBeTruthy();
+ });
+
+ it("creates properties view", () => {
+ const propertiesView = htmlElement.querySelector("#sf-properties-view");
+ expect(propertiesView).toBeTruthy();
+ });
+});
diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.ts
new file mode 100644
index 000000000..7017441aa
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.component.ts
@@ -0,0 +1,60 @@
+/*
+ * 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 {
+ Component,
+ Input
+} from "@angular/core";
+import { UiData } from "./ui_data";
+import { TRACE_INFO } from "app/trace_info";
+import { TraceType } from "common/trace/trace_type";
+
+@Component({
+ selector: "viewer-surface-flinger",
+ template: `
+
+ `,
+ styles: [
+ "@import 'https://fonts.googleapis.com/icon?family=Material+Icons';",
+ "mat-icon {margin: 5px}",
+ "viewer-surface-flinger {font-family: Arial, Helvetica, sans-serif;}",
+ ".trace-card-title {display: inline-block; vertical-align: middle;}",
+ ".header-button {background: none; border: none; display: inline-block; vertical-align: middle;}",
+ ".card-grid {width: 100%;height: 100%;display: flex;flex-direction: row;overflow: auto;}",
+ ".rects-view {font: inherit; flex: none !important;width: 400px;margin: 8px;}",
+ ".hierarchy-view, .properties-view {font: inherit; flex: 1;margin: 8px;min-width: 400px;min-height: 50rem;max-height: 50rem;}",
+ ]
+})
+export class ViewerSurfaceFlingerComponent {
+ @Input()
+ inputData?: UiData;
+
+ TRACE_INFO = TRACE_INFO;
+ TraceType = TraceType;
+}
diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.ts
new file mode 100644
index 000000000..5d49f6edd
--- /dev/null
+++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/viewer_surface_flinger.ts
@@ -0,0 +1,51 @@
+/*
+ * 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 {TraceType} from "common/trace/trace_type";
+import {Viewer} from "viewers/viewer";
+import {Presenter} from "./presenter";
+import {UiData} from "./ui_data";
+
+class ViewerSurfaceFlinger implements Viewer {
+ constructor() {
+ this.view = document.createElement("viewer-surface-flinger");
+ this.presenter = new Presenter((uiData: UiData) => {
+ (this.view as any).inputData = uiData;
+ });
+ this.view.addEventListener("highlightedChange", (event) => this.presenter.updateHighlightedRect((event as CustomEvent)));
+ }
+
+ public notifyCurrentTraceEntries(entries: Map): void {
+ this.presenter.notifyCurrentTraceEntries(entries);
+ }
+
+ public getView(): HTMLElement {
+ return this.view;
+ }
+
+ public getTitle(): string {
+ return "Surface Flinger";
+ }
+
+ public getDependencies(): TraceType[] {
+ return ViewerSurfaceFlinger.DEPENDENCIES;
+ }
+
+ public static readonly DEPENDENCIES: TraceType[] = [TraceType.SURFACE_FLINGER];
+ private view: HTMLElement;
+ private presenter: Presenter;
+}
+
+export {ViewerSurfaceFlinger};
diff --git a/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.ts b/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.ts
index 30bf20fd9..04f407236 100644
--- a/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.ts
+++ b/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.ts
@@ -16,29 +16,29 @@
import {TraceType} from "common/trace/trace_type";
import {UiData} from "./ui_data";
-type UiDataCallbackType = (uiData: UiData) => void;
+type NotifyViewCallbackType = (uiData: UiData) => void;
class Presenter {
- constructor(uiDataCallback: UiDataCallbackType) {
- this.uiDataCallback = uiDataCallback;
+ constructor(notifyViewCallback: NotifyViewCallbackType) {
+ this.notifyViewCallback = notifyViewCallback;
this.uiData = new UiData("Initial UI data");
- this.uiDataCallback(this.uiData);
+ this.notifyViewCallback(this.uiData);
}
public notifyCurrentTraceEntries(entries: Map) {
this.uiData = new UiData("UI data selected by user on time scrub");
- this.uiDataCallback(this.uiData);
+ this.notifyViewCallback(this.uiData);
}
public notifyUiEvent() {
const oldUiDataText = this.uiData ? this.uiData.text : "";
this.uiData = new UiData(oldUiDataText);
this.uiData.text += " | UI data updated because of UI event";
- this.uiDataCallback(this.uiData!);
+ this.notifyViewCallback(this.uiData!);
}
- private readonly uiDataCallback: UiDataCallbackType;
- private uiData?: UiData;
+ readonly notifyViewCallback: NotifyViewCallbackType;
+ uiData?: UiData;
}
-export {Presenter, UiDataCallbackType};
+export {Presenter};
diff --git a/tools/winscope-ng/src/viewers/viewer_window_manager/ui_data.ts b/tools/winscope-ng/src/viewers/viewer_window_manager/ui_data.ts
index 22c97e055..6c36a228a 100644
--- a/tools/winscope-ng/src/viewers/viewer_window_manager/ui_data.ts
+++ b/tools/winscope-ng/src/viewers/viewer_window_manager/ui_data.ts
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
class UiData {
constructor(public text: string) {
+ console.log("new UI data", text);
}
}
diff --git a/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.component.spec.ts b/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.component.spec.ts
index 15d8f5b05..ba087b969 100644
--- a/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.component.spec.ts
+++ b/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.component.spec.ts
@@ -50,5 +50,4 @@ describe("ViewerWindowManagerComponent", () => {
const divInputValue = htmlElement.querySelector(".viewer-window-manager div.input-value");
expect(divInputValue?.innerHTML).toContain("UI Data Value");
});
-
});
diff --git a/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.component.ts b/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.component.ts
index 9e2e3270e..00fe4467b 100644
--- a/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.component.ts
+++ b/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.component.ts
@@ -22,10 +22,11 @@ import {
import {UiData} from "./ui_data";
@Component({
+ selector: "viewer-window-manager",
template: `
Window Manager
-
Input value: {{inputData.text}}
+
Input value: {{inputData?.text}}
`
diff --git a/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.ts b/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.ts
index 1f4b1c400..9cfbb06fe 100644
--- a/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.ts
+++ b/tools/winscope-ng/src/viewers/viewer_window_manager/viewer_window_manager.ts
@@ -27,6 +27,10 @@ class ViewerWindowManager implements Viewer {
this.view.addEventListener("outputEvent", () => this.presenter.notifyUiEvent());
}
+ public getTitle() {
+ return "Window Manager";
+ }
+
public notifyCurrentTraceEntries(entries: Map): void {
this.presenter.notifyCurrentTraceEntries(entries);
}
@@ -35,6 +39,10 @@ class ViewerWindowManager implements Viewer {
return this.view;
}
+ public getDependencies(): TraceType[] {
+ return ViewerWindowManager.DEPENDENCIES;
+ }
+
public static readonly DEPENDENCIES: TraceType[] = [TraceType.WINDOW_MANAGER];
private view: HTMLElement;
private presenter: Presenter;