Merge "winscope-ng initial commit"

This commit is contained in:
Kean Mariotti
2022-07-05 05:38:09 +00:00
committed by Android (Google) Code Review
88 changed files with 25993 additions and 0 deletions

44
tools/winscope-ng/.gitignore vendored Normal file
View File

@@ -0,0 +1,44 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
# Kotlin transpiled code
/kotlin_build
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
# System files
.DS_Store
Thumbs.db

View File

@@ -0,0 +1,42 @@
# Tool for visualizing window manager traces
## Developing WinScope
When the trace is enabled, Window Manager and Surface Flinger capture and
save current state to a file at each point of interest.
`frameworks/base/core/proto/android/server/windowmanagertrace.proto`
and `frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto`
contain the proto definitions for their internal states.
### Checking out code and setting up environment
* [Download Android source](https://source.android.com/setup/build/downloading)
* Navigate to `development/tools/winscope`
* Run `npm install`
### Build & test & deploy changes
* Navigate to `development/tools/winscope`
* Run `npm run` to get the list of available commands
### Update IntDefMapping
* Build `framework-minus-apex-intdefs` module and a preprocessor will
generate the latest IntDefMapping. From the `ANDROID_ROOT` run:
```
. build/envsetup.sh
m framework-minus-apex-intdefs
```
* Copy the generated `intDefMapping.json` files to the `prebuilts` repo.
```
python3 -c 'import sys,json,collections; print(json.dumps(collections.OrderedDict(sorted(collections.ChainMap(*map(lambda x:json.load(open(x)), sys.argv[1:])).items())), indent=2))' $(find out/soong/.intermediates/frameworks/base -iname intDefMapping.json) > ./prebuilts/misc/common/winscope/intDefMapping.json
```
* Upload the changes.
```
cd ./prebuilts/misc/common/winscope
repo start intdef-update
git commit -am "Update intdef mapping" "Test: N/A"
repo upload --cbr .
```
### Building with internal extensions
Internal paths in vendor/ which are not available in AOSP must be replaced by
stub files. See getWaylandSafePath for an example

View File

@@ -0,0 +1,39 @@
/*
* 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.
*/
const glob = require("glob");
let webpackConfig = require("./webpack.config.common");
delete webpackConfig.entry;
delete webpackConfig.output;
module.exports = (config) => {
config.set({
frameworks: ["jasmine", "webpack"],
plugins: [
"karma-webpack",
"karma-chrome-launcher",
"karma-jasmine",
"karma-sourcemap-loader",
],
files: [{ pattern: "src/main.component.spec.ts", watched: false }],
preprocessors: {
'src/main.component.spec.ts': ['webpack', 'sourcemap']
},
singleRun: true,
browsers: ["ChromeHeadless"],
webpack: webpackConfig
});
}

View File

@@ -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.
*/
const fs = require('fs');
const protobuf = require('protobufjs');
const loaderUtils = require('loader-utils');
module.exports = function(source) {
const webpackContext = this;
const root = new protobuf.Root();
const paths = loaderUtils.getOptions(this)['paths'] || [];
root.resolvePath = function resolvePath(origin, target) {
const normOrigin = protobuf.util.path.normalize(origin);
const normTarget = protobuf.util.path.normalize(target);
let candidates = [
protobuf.util.path.resolve(normOrigin, normTarget, true)
];
candidates = candidates.concat(
paths.map(path => protobuf.util.path.resolve(path + "/", target))
);
for (const path of candidates) {
if (fs.existsSync(path)) {
webpackContext.addDependency(path);
return path;
}
}
throw Error(`Failed to resolve path: origin=${origin}, target=${target}, candidates=${candidates}`);
};
root.loadSync(webpackContext.resourcePath).resolveAll();
const result = JSON.stringify(root, null, 2);
return `module.exports = ${result}`;
};

21555
tools/winscope-ng/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
{
"name": "winscope-ng",
"version": "0.0.0",
"scripts": {
"build:kotlin": "npx kotlinc-js -source-map -source-map-embed-sources always -module-kind commonjs -output kotlin_build/flicker.js ../../../platform_testing/libraries/flicker/src/com/android/server/wm/traces/common",
"build:prod": "npm run build:kotlin && cross-env NODE_ENV=production webpack --progress",
"start": "cross-env NODE_ENV=development webpack serve --open --hot",
"test:unit": "cross-env NODE_ENV=development webpack --config webpack.config.spec.js && jasmine dist/bundle.spec.js",
"test:component": "npx karma start",
"test:all": "npm run test:unit && npm run test:component"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.0.0",
"@angular/common": "^14.0.0",
"@angular/compiler": "^14.0.0",
"@angular/core": "^14.0.1",
"@angular/elements": "^14.0.1",
"@angular/forms": "^14.0.0",
"@angular/platform-browser": "^14.0.0",
"@angular/platform-browser-dynamic": "^14.0.0",
"@angular/router": "^14.0.0",
"angular2-template-loader": "^0.6.2",
"cross-env": "^7.0.3",
"html-loader": "^3.1.0",
"html-webpack-plugin": "^5.5.0",
"kotlin": "^1.7.0",
"kotlin-compiler": "^1.7.0",
"loader-utils": "^2.0.0",
"protobufjs": "^6.11.3",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"ts-loader": "^9.3.0",
"typescript": "~4.7.2",
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.0.0",
"@angular/cli": "~14.0.0",
"@angular/compiler-cli": "^14.0.0",
"@types/jasmine": "~4.0.0",
"jasmine": "^4.2.1",
"jasmine-core": "~4.1.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-jasmine": "~5.0.0",
"karma-sourcemap-loader": "^0.3.8",
"karma-webpack": "^5.0.0"
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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 {AppComponent} from './app.component';
describe("AppComponent", () => {
let fixture: ComponentFixture<AppComponent>;
let component: AppComponent;
let htmlElement: HTMLElement;
beforeAll(async () => {
await TestBed.configureTestingModule({
declarations: [
AppComponent
],
}).compileComponents();
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
htmlElement = fixture.nativeElement;
});
it("can be created", () => {
expect(component).toBeTruthy();
});
it("has the expected title", () => {
expect(component.title).toEqual("winscope-ng");
});
it("renders the title", () => {
expect(htmlElement.querySelector("div#title")?.innerHTML).toContain("Winscope Viewer 2.0");
});
});

View File

@@ -0,0 +1,95 @@
/*
* 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} from '@angular/core';
import {createCustomElement} from '@angular/elements';
import {ViewerWindowManagerComponent} from 'viewers/viewer_window_manager/viewer_window_manager.component';
import {Core} from './core';
@Component({
selector: 'app-root',
template: `
<div id="title">
<span>Winscope Viewer 2.0</span>
</div>
<div id="inputfile">
<input type="file" (change)="onInputFile($event)" #fileUpload>
</div>
<div id="timescrub">
<button (click)="notifyCurrentTimestamp()">Update current timestamp</button>
</div>
<div id="viewers">
</div>
<div id="timestamps">
</div>
`
})
export class AppComponent {
title = 'winscope-ng';
private core!: Core;
constructor(@Inject(Injector) injector: Injector) {
customElements.define('viewer-window-manager',
createCustomElement(ViewerWindowManagerComponent, {injector}));
}
public async onInputFile(event: Event) {
const buffers = await this.readInputFiles(event);
this.core = new Core();
await this.core.bootstrap(buffers);
const viewersDiv = document.querySelector("div#viewers")!;
viewersDiv.innerHTML = "";
this.core.getViews().forEach(view => viewersDiv!.appendChild(view) );
const timestampsDiv = document.querySelector("div#timestamps")!;
timestampsDiv.innerHTML = `Retrieved ${this.core.getTimestamps().length} unique timestamps`;
}
public notifyCurrentTimestamp() {
const dummyTimestamp = 1000000; //TODO: get timestamp from time scrub
this.core.notifyCurrentTimestamp(dummyTimestamp)
}
//TODO: extend with support for multiple files, archives, etc...
private async readInputFiles(event: Event): Promise<Uint8Array[]> {
const files: any = (event?.target as HTMLInputElement)?.files;
if (!files || !files[0]) {
console.log("Invalid input file");
return [];
}
const file: File = files[0];
const buffer: Uint8Array = await new Promise((resolve, _) => {
const reader = new FileReader();
reader.onloadend = async (e) => {
const buffer = new Uint8Array(<ArrayBuffer> e.target!.result);
resolve(buffer);
};
reader.readAsArrayBuffer(file);
});
return [buffer];
}
}

View File

@@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ViewerWindowManagerComponent} from 'viewers/viewer_window_manager/viewer_window_manager.component';
@NgModule({
declarations: [
AppComponent,
ViewerWindowManagerComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

View File

@@ -0,0 +1,76 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Parser} from 'parsers/parser'
import {ParserFactory} from 'parsers/parser_factory'
import {Viewer} from 'viewers/viewer'
import {ViewerFactory} from 'viewers/viewer_factory'
class Core {
private parsers: Parser[];
private viewers: Viewer[];
constructor() {
this.parsers = [];
this.viewers = [];
}
async bootstrap(buffers: Uint8Array[]) {
this.parsers = new ParserFactory().createParsers(buffers);
console.log("created parsers: ", this.parsers);
const activeTraceTypes = this.parsers.map(parser => parser.getTraceTypeId());
console.log("active trace types: ", activeTraceTypes);
this.viewers = new ViewerFactory().createViewers(new Set<TraceTypeId>(activeTraceTypes));
console.log("created viewers: ", this.viewers);
}
getViews(): HTMLElement[] {
return this.viewers.map(viewer => viewer.getView());
}
getTimestamps(): number[] {
const mergedTimestamps: number[] = [];
this.parsers
.map(parser => parser.getTimestamps())
.forEach(timestamps => {
mergedTimestamps.push(...timestamps);
});
const uniqueTimestamps = [... new Set<number>(mergedTimestamps)];
return uniqueTimestamps;
}
notifyCurrentTimestamp(timestamp: number) {
const traceEntries: Map<TraceTypeId, any> = new Map<TraceTypeId, any>();
this.parsers.forEach(parser => {
const entry = parser.getTraceEntry(timestamp);
if (entry != undefined) {
traceEntries.set(parser.getTraceTypeId(), entry);
}
});
this.viewers.forEach(viewer => {
viewer.notifyCurrentTraceEntries(traceEntries);
})
}
}
export { Core };

View File

View File

@@ -0,0 +1,35 @@
{
"invalidProperties": [
"length",
"prototype",
"ref",
"diff",
"rects",
"chips",
"parent",
"timestamp",
"shortName",
"kind",
"resolvedChildren",
"visibilityReason",
"absoluteZ",
"children",
"stableId"
],
"intDefColumn": {
"WindowLayoutParams.type": "android.view.WindowManager.LayoutParams.WindowType",
"WindowLayoutParams.flags": "android.view.WindowManager.LayoutParams.Flags",
"WindowLayoutParams.privateFlags": "android.view.WindowManager.LayoutParams.PrivateFlags",
"WindowLayoutParams.gravity": "android.view.Gravity.GravityFlags",
"WindowLayoutParams.softInputMode": "android.view.WindowManager.LayoutParams.WindowType",
"WindowLayoutParams.systemUiVisibilityFlags": "android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags",
"WindowLayoutParams.subtreeSystemUiVisibilityFlags": "android.view.WindowManager.LayoutParams.SystemUiVisibilityFlags",
"WindowLayoutParams.behavior": "android.view.WindowInsetsController.Behavior",
"WindowLayoutParams.fitInsetsSides": "android.view.WindowInsets.Side.InsetsSide",
"Configuration.windowingMode": "android.app.WindowConfiguration.WindowingMode",
"WindowConfiguration.windowingMode": "android.app.WindowConfiguration.WindowingMode",
"Configuration.orientation": "android.content.pm.ActivityInfo.ScreenOrientation",
"WindowConfiguration.orientation": "android.content.pm.ActivityInfo.ScreenOrientation",
"WindowState.orientation": "android.content.pm.ActivityInfo.ScreenOrientation"
}
}

View File

@@ -0,0 +1,278 @@
/*
* Copyright 2021, 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 {toSize, toActiveBuffer, toColor, toColor3, toPoint, toRect,
toRectF, toRegion, toMatrix22, toTransform} from './common';
import intDefMapping from
'../../../../../../../prebuilts/misc/common/winscope/intDefMapping.json';
import config from './Configuration.json'
function readIntdefMap(): Map<string, string> {
const map = new Map<string, string>();
const keys = Object.keys(config.intDefColumn);
keys.forEach(key => {
const value = config.intDefColumn[<keyof typeof config.intDefColumn>key];
map.set(key, value);
});
return map;
}
export default class ObjectFormatter {
static displayDefaults: boolean = false
private static INVALID_ELEMENT_PROPERTIES = config.invalidProperties;
private static FLICKER_INTDEF_MAP = readIntdefMap();
static cloneObject(entry: any): any {
let obj: any = {}
const properties = ObjectFormatter.getProperties(entry);
properties.forEach(prop => obj[prop] = entry[prop]);
return obj;
}
/**
* Get the true properties of an entry excluding functions, kotlin gernerated
* variables, explicitly excluded properties, and flicker objects already in
* the hierarchy that shouldn't be traversed when formatting the entry
* @param entry The entry for which we want to get the properties for
* @return The "true" properties of the entry as described above
*/
static getProperties(entry: any): string[] {
const props: string[] = [];
let obj = entry;
do {
const properties = Object.getOwnPropertyNames(obj).filter(it => {
// filter out functions
if (typeof(entry[it]) === 'function') return false;
// internal propertires from kotlinJs
if (it.includes(`$`)) return false;
// private kotlin variables from kotlin
if (it.startsWith(`_`)) return false;
// some predefined properties used only internally (e.g., children, ref, diff)
if (this.INVALID_ELEMENT_PROPERTIES.includes(it)) return false;
const value = entry[it];
// only non-empty arrays of non-flicker objects (otherwise they are in hierarchy)
if (Array.isArray(value) && value.length > 0) return !value[0].stableId;
// non-flicker object
return !(value?.stableId);
});
properties.forEach(function (prop) {
if (typeof(entry[prop]) !== 'function' && props.indexOf(prop) === -1) {
props.push(prop);
}
});
} while (obj = Object.getPrototypeOf(obj));
return props;
}
/**
* Format a Winscope entry to be displayed in the UI
* Accounts for different user display settings (e.g. hiding empty/default values)
* @param obj The raw object to format
* @return The formatted object
*/
static format(obj: any): {} {
const properties = this.getProperties(obj);
const sortedProperties = properties.sort()
const result: any = {}
sortedProperties.forEach(entry => {
const key = entry;
const value: any = obj[key];
if (value === null || value === undefined) {
if (this.displayDefaults) {
result[key] = value;
}
return
}
if (value || this.displayDefaults) {
// raw values (e.g., false or 0)
if (!value) {
result[key] = value
// flicker obj
} else if (value.prettyPrint) {
const isEmpty = value.isEmpty === true;
if (!isEmpty || this.displayDefaults) {
result[key] = value.prettyPrint();
}
} else {
// converted proto to flicker
const translatedObject = this.translateObject(value);
if (translatedObject) {
if (translatedObject.prettyPrint) {
result[key] = translatedObject.prettyPrint();
}
else {
result[key] = translatedObject;
}
// objects - recursive call
} else if (value && typeof(value) == `object`) {
const childObj = this.format(value) as any;
const isEmpty = Object.entries(childObj).length == 0 || childObj.isEmpty;
if (!isEmpty || this.displayDefaults) {
result[key] = childObj;
}
} else {
// values
result[key] = this.translateIntDef(obj, key, value);
}
}
}
})
return result;
}
/**
* Translate some predetermined proto objects into their flicker equivalent
*
* Returns null if the object cannot be translated
*
* @param obj Object to translate
*/
private static translateObject(obj: any) {
const type = obj?.$type?.name;
switch(type) {
case `SizeProto`: return toSize(obj);
case `ActiveBufferProto`: return toActiveBuffer(obj);
case `Color3`: return toColor3(obj);
case `ColorProto`: return toColor(obj);
case `PointProto`: return toPoint(obj);
case `RectProto`: return toRect(obj);
case `Matrix22`: return toMatrix22(obj);
case `FloatRectProto`: return toRectF(obj);
case `RegionProto`: return toRegion(obj);
case `TransformProto`: return toTransform(obj);
case 'ColorTransformProto': {
const formatted = this.formatColorTransform(obj.val);
return `${formatted}`;
}
}
return null;
}
private static formatColorTransform(vals: any) {
const fixedVals = vals.map((v: any) => v.toFixed(1));
let formatted = ``;
for (let i = 0; i < fixedVals.length; i += 4) {
formatted += `[`;
formatted += fixedVals.slice(i, i + 4).join(', ');
formatted += `] `;
}
return formatted;
}
/**
* Obtains from the proto field, the metadata related to the typedef type (if any)
*
* @param obj Proto object
* @param propertyName Property to search
*/
private static getTypeDefSpec(obj: any, propertyName: string): string|null {
const fields = obj?.$type?.fields;
if (!fields) {
return null;
}
const options = fields[propertyName]?.options;
if (!options) {
return null;
}
return options["(.android.typedef)"];
}
/**
* Translate intdef properties into their string representation
*
* For proto objects check the
*
* @param parentObj Object containing the value to parse
* @param propertyName Property to search
* @param value Property value
*/
private static translateIntDef(parentObj: any, propertyName: string, value: any): string {
const parentClassName = parentObj.constructor.name;
const propertyPath = `${parentClassName}.${propertyName}`;
let translatedValue: string = value;
// Parse Flicker objects (no intdef annotation supported)
if (this.FLICKER_INTDEF_MAP.has(propertyPath)) {
translatedValue = this.getIntFlagsAsStrings(value,
<string>this.FLICKER_INTDEF_MAP.get(propertyPath));
} else {
// If it's a proto, search on the proto definition for the intdef type
const typeDefSpec = this.getTypeDefSpec(parentObj, propertyName);
if (typeDefSpec) {
translatedValue = this.getIntFlagsAsStrings(value, typeDefSpec);
}
}
return translatedValue;
}
/**
* Translate a property from its numerical value into its string representation
*
* @param intFlags Property value
* @param annotationType IntDef type to use
*/
private static getIntFlagsAsStrings(intFlags: any, annotationType: string): string {
const flags = [];
const mapping = intDefMapping[<keyof typeof intDefMapping>annotationType].values;
const knownFlagValues = Object.keys(mapping).reverse().map(x => parseInt(x));
if (knownFlagValues.length == 0) {
console.warn("No mapping for type", annotationType)
return intFlags + ""
}
// Will only contain bits that have not been associated with a flag.
const parsedIntFlags = parseInt(intFlags);
let leftOver = parsedIntFlags;
for (const flagValue of knownFlagValues) {
if (((leftOver & flagValue) && ((intFlags & flagValue) === flagValue))
|| (parsedIntFlags === 0 && flagValue === 0)) {
flags.push(mapping[<keyof typeof mapping>flagValue]);
leftOver = leftOver & ~flagValue;
}
}
if (flags.length === 0) {
console.error('No valid flag mappings found for ',
intFlags, 'of type', annotationType);
}
if (leftOver) {
// If 0 is a valid flag value that isn't in the intDefMapping
// it will be ignored
flags.push(leftOver);
}
return flags.join(' | ');
}
}

View File

@@ -0,0 +1,12 @@
This directory contains all the code extending the common Flicker library
to make it fully compatible with Winscope. The common Flicker library is
written is Kotlin and compiled to JavaScript and then extended by the code in
this directory.
To use flickerlib in the rest of the Winscope source code use
`import { ... } from '@/flickerlib'` rather than importing the compiled
common Flicker library directly.
The flickerlib classes are extended through mixins (functions, getter, and
setters) that are injected into the original compiled common Flicker library
classes.

View File

@@ -0,0 +1,313 @@
/*
* Copyright 2020, 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.
*/
// Imports all the compiled common Flicker library classes and exports them
// as clean es6 modules rather than having them be commonjs modules
// WM
const WindowManagerTrace = require('flicker').com.android.server.wm.traces.
common.windowmanager.WindowManagerTrace;
const WindowManagerState = require('flicker').com.android.server.wm.traces.
common.windowmanager.WindowManagerState;
const Activity = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.Activity;
const Configuration = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.Configuration;
const ConfigurationContainer = require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.ConfigurationContainer;
const DisplayArea = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.DisplayArea;
const DisplayContent = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.DisplayContent;
const KeyguardControllerState = require('flicker').com.android.server.wm.
traces.common.windowmanager.windows.KeyguardControllerState;
const RootWindowContainer = require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.RootWindowContainer;
const Task = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.Task;
const TaskFragment = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.TaskFragment;
const WindowConfiguration = require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.WindowConfiguration;
const WindowContainer = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.WindowContainer;
const WindowLayoutParams= require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.WindowLayoutParams;
const WindowManagerPolicy = require('flicker').com.android.server.wm.traces.
common.windowmanager.windows.WindowManagerPolicy;
const WindowState = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.WindowState;
const WindowToken = require('flicker').com.android.server.wm.traces.common.
windowmanager.windows.WindowToken;
// SF
const Layer = require('flicker').com.android.server.wm.traces.common.
layers.Layer;
const BaseLayerTraceEntry = require('flicker').com.android.server.wm.traces.common.
layers.BaseLayerTraceEntry;
const LayerTraceEntry = require('flicker').com.android.server.wm.traces.common.
layers.LayerTraceEntry;
const LayerTraceEntryBuilder = require('flicker').com.android.server.wm.traces.
common.layers.LayerTraceEntryBuilder;
const LayersTrace = require('flicker').com.android.server.wm.traces.common.
layers.LayersTrace;
const Matrix22 = require('flicker').com.android.server.wm.traces.common
.Matrix22;
const Matrix33 = require('flicker').com.android.server.wm.traces.common
.Matrix33;
const Transform = require('flicker').com.android.server.wm.traces.common.
layers.Transform;
const Display = require('flicker').com.android.server.wm.traces.common.
layers.Display;
// Common
const Size = require('flicker').com.android.server.wm.traces.common.Size;
const ActiveBuffer = require('flicker').com.android.server.wm.traces.common
.ActiveBuffer;
const Color3 = require('flicker').com.android.server.wm.traces.common.Color3;
const Color = require('flicker').com.android.server.wm.traces.common.Color;
const Point = require('flicker').com.android.server.wm.traces.common.Point;
const Rect = require('flicker').com.android.server.wm.traces.common.Rect;
const RectF = require('flicker').com.android.server.wm.traces.common.RectF;
const Region = require('flicker').com.android.server.wm.traces.common.region.Region;
//Tags
const Tag = require('flicker').com.android.server.wm.traces.common.tags.Tag;
const TagState = require('flicker').com.android.server.wm.traces.common.tags.TagState;
const TagTrace = require('flicker').com.android.server.wm.traces.common.tags.TagTrace;
//Errors
const Error = require('flicker').com.android.server.wm.traces.common.errors.Error;
const ErrorState = require('flicker').com.android.server.wm.traces.common.errors.ErrorState;
const ErrorTrace = require('flicker').com.android.server.wm.traces.common.errors.ErrorTrace;
// Service
const TaggingEngine = require('flicker').com.android.server.wm.traces.common.service.TaggingEngine;
const EMPTY_BUFFER = new ActiveBuffer(0, 0, 0, 0);
const EMPTY_COLOR3 = new Color3(-1, -1, -1);
const EMPTY_COLOR = new Color(-1, -1, -1, 0);
const EMPTY_RECT = new Rect(0, 0, 0, 0);
const EMPTY_RECTF = new RectF(0, 0, 0, 0);
const EMPTY_POINT = new Point(0, 0);
const EMPTY_MATRIX22 = new Matrix22(0, 0, 0, 0, 0, 0);
const EMPTY_MATRIX33 = new Matrix33(0, 0, 0, 0, 0, 0);
const EMPTY_TRANSFORM = new Transform(0, EMPTY_MATRIX33);
function toSize(proto) {
if (proto == null) {
return EMPTY_BOUNDS;
}
const width = proto.width ?? proto.w ?? 0;
const height = proto.height ?? proto.h ?? 0;
if (width || height) {
return new Size(width, height);
}
return EMPTY_BOUNDS;
}
function toActiveBuffer(proto) {
const width = proto?.width ?? 0;
const height = proto?.height ?? 0;
const stride = proto?.stride ?? 0;
const format = proto?.format ?? 0;
if (width || height || stride || format) {
return new ActiveBuffer(width, height, stride, format);
}
return EMPTY_BUFFER;
}
function toColor3(proto) {
if (proto == null) {
return EMPTY_COLOR;
}
const r = proto.r ?? 0;
const g = proto.g ?? 0;
const b = proto.b ?? 0;
if (r || g || b) {
return new Color3(r, g, b);
}
return EMPTY_COLOR3;
}
function toColor(proto) {
if (proto == null) {
return EMPTY_COLOR;
}
const r = proto.r ?? 0;
const g = proto.g ?? 0;
const b = proto.b ?? 0;
const a = proto.a ?? 0;
if (r || g || b || a) {
return new Color(r, g, b, a);
}
return EMPTY_COLOR;
}
function toPoint(proto) {
if (proto == null) {
return null;
}
const x = proto.x ?? 0;
const y = proto.y ?? 0;
if (x || y) {
return new Point(x, y);
}
return EMPTY_POINT;
}
function toRect(proto) {
if (proto == null) {
return EMPTY_RECT;
}
const left = proto?.left ?? 0;
const top = proto?.top ?? 0;
const right = proto?.right ?? 0;
const bottom = proto?.bottom ?? 0;
if (left || top || right || bottom) {
return new Rect(left, top, right, bottom);
}
return EMPTY_RECT;
}
function toRectF(proto) {
if (proto == null) {
return EMPTY_RECTF;
}
const left = proto?.left ?? 0;
const top = proto?.top ?? 0;
const right = proto?.right ?? 0;
const bottom = proto?.bottom ?? 0;
if (left || top || right || bottom) {
return new RectF(left, top, right, bottom);
}
return EMPTY_RECTF;
}
function toRegion(proto) {
if (proto == null) {
return null;
}
const rects = [];
for (let x = 0; x < proto.rect.length; x++) {
const rect = proto.rect[x];
const parsedRect = toRect(rect);
rects.push(parsedRect);
}
return new Region(rects);
}
function toTransform(proto) {
if (proto == null) {
return EMPTY_TRANSFORM;
}
const dsdx = proto.dsdx ?? 0;
const dtdx = proto.dtdx ?? 0;
const tx = proto.tx ?? 0;
const dsdy = proto.dsdy ?? 0;
const dtdy = proto.dtdy ?? 0;
const ty = proto.ty ?? 0;
if (dsdx || dtdx || tx || dsdy || dtdy || ty) {
const matrix = new Matrix33(dsdx, dtdx, tx, dsdy, dtdy, ty);
return new Transform(proto.type ?? 0, matrix);
}
if (proto.type) {
return new Transform(proto.type ?? 0, EMPTY_MATRIX33);
}
return EMPTY_TRANSFORM;
}
function toMatrix22(proto) {
if (proto == null) {
return EMPTY_MATRIX22;
}
const dsdx = proto.dsdx ?? 0;
const dtdx = proto.dtdx ?? 0;
const dsdy = proto.dsdy ?? 0;
const dtdy = proto.dtdy ?? 0;
if (dsdx || dtdx || dsdy || dtdy) {
return new Matrix22(dsdx, dtdx, dsdy, dtdy);
}
return EMPTY_MATRIX22;
}
export {
Activity,
Configuration,
ConfigurationContainer,
DisplayArea,
DisplayContent,
KeyguardControllerState,
RootWindowContainer,
Task,
TaskFragment,
WindowConfiguration,
WindowContainer,
WindowState,
WindowToken,
WindowLayoutParams,
WindowManagerPolicy,
WindowManagerTrace,
WindowManagerState,
// SF
BaseLayerTraceEntry,
Layer,
LayerTraceEntry,
LayerTraceEntryBuilder,
LayersTrace,
Transform,
Matrix22,
Matrix33,
Display,
// Tags
Tag,
TagState,
TagTrace,
// Errors
Error,
ErrorState,
ErrorTrace,
// Common
Size,
ActiveBuffer,
Color,
Color3,
Point,
Rect,
RectF,
Region,
// Service
TaggingEngine,
toSize,
toActiveBuffer,
toColor,
toColor3,
toPoint,
toRect,
toRectF,
toRegion,
toMatrix22,
toTransform,
};

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2021, 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 { Error } from "../common"
Error.fromProto = function (proto: any): Error {
const error = new Error(
proto.stacktrace,
proto.message,
proto.layerId,
proto.windowToken,
proto.taskId,
proto.assertionName
);
return error;
}
export default Error;

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2020, 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 { ErrorState } from "../common";
import Error from './Error';
ErrorState.fromProto = function (protos: any[], timestamp: number): ErrorState {
const errors = protos.map(it => Error.fromProto(it));
const state = new ErrorState(errors, `${timestamp}`);
return state;
}
export default ErrorState;

View File

@@ -0,0 +1,109 @@
/*
* Copyright 2021, 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 { Layer, Rect, toActiveBuffer, toColor, toRect, toRectF, toRegion } from "../common"
import { shortenName } from '../mixin'
import { RELATIVE_Z_CHIP, GPU_CHIP, HWC_CHIP } from '../treeview/Chips'
import Transform from './Transform'
Layer.fromProto = function (proto: any): Layer {
const visibleRegion = toRegion(proto.visibleRegion)
const activeBuffer = toActiveBuffer(proto.activeBuffer)
const bounds = toRectF(proto.bounds)
const color = toColor(proto.color)
const screenBounds = toRectF(proto.screenBounds)
const sourceBounds = toRectF(proto.sourceBounds)
const transform = Transform.fromProto(proto.transform, proto.position)
const bufferTransform = Transform.fromProto(proto.bufferTransform, /* position */ null)
const hwcCrop = toRectF(proto.hwcCrop)
const hwcFrame = toRect(proto.hwcFrame)
const requestedColor = toColor(proto.requestedColor)
const requestedTransform =
Transform.fromProto(proto.requestedTransform, proto.requestedPosition)
const cornerRadiusCrop = toRectF(proto.cornerRadiusCrop)
const inputTransform =
Transform.fromProto(proto.inputWindowInfo ? proto.inputWindowInfo.transform : null)
const inputRegion =
toRegion(proto.inputWindowInfo ? proto.inputWindowInfo.touchableRegion : null)
let crop: Rect
if (proto.crop) {
crop = toRect(proto.crop)
};
const entry = new Layer(
proto.name ?? ``,
proto.id,
proto.parent,
proto.z,
visibleRegion,
activeBuffer,
proto.flags,
bounds,
color,
proto.isOpaque,
proto.shadowRadius,
proto.cornerRadius,
proto.type ?? ``,
screenBounds,
transform,
sourceBounds,
proto.currFrame,
proto.effectiveScalingMode,
bufferTransform,
proto.hwcCompositionType,
hwcCrop,
hwcFrame,
proto.backgroundBlurRadius,
crop,
proto.isRelativeOf,
proto.zOrderRelativeOf,
proto.layerStack,
requestedTransform,
requestedColor,
cornerRadiusCrop,
inputTransform,
inputRegion,
);
addAttributes(entry, proto);
return entry
}
function addAttributes(entry: Layer, proto: any) {
entry.kind = `${entry.id}`;
entry.shortName = shortenName(entry.name);
entry.proto = proto;
entry.rect = entry.bounds;
entry.rect.transform = entry.transform;
entry.rect.ref = entry;
entry.rect.label = entry.name;
entry.chips = [];
updateChips(entry);
}
function updateChips(entry: Layer) {
if ((entry.zOrderRelativeOf || -1) !== -1) {
entry.chips.push(RELATIVE_Z_CHIP);
}
if (entry.hwcCompositionType === 'CLIENT') {
entry.chips.push(GPU_CHIP);
} else if (entry.hwcCompositionType === 'DEVICE' || entry.hwcCompositionType === 'SOLID_COLOR') {
entry.chips.push(HWC_CHIP);
}
}
export default Layer;

View File

@@ -0,0 +1,80 @@
/*
* Copyright 2021, 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 { Display, LayerTraceEntry, LayerTraceEntryBuilder, toRect, toSize, toTransform } from "../common"
import Layer from './Layer'
import { VISIBLE_CHIP, RELATIVE_Z_PARENT_CHIP, MISSING_LAYER } from '../treeview/Chips'
LayerTraceEntry.fromProto = function (protos: any[], displayProtos: any[],
timestamp: number, hwcBlob: string, where: string = ''): LayerTraceEntry {
const layers = protos.map(it => Layer.fromProto(it));
const displays = (displayProtos || []).map(it => newDisplay(it));
const builder = new LayerTraceEntryBuilder(timestamp, layers, displays, hwcBlob, where);
const entry: LayerTraceEntry = builder.build();
updateChildren(entry);
addAttributes(entry, protos);
return entry;
}
function addAttributes(entry: LayerTraceEntry, protos: any) {
entry.kind = "entry"
// There no JVM/JS translation for Longs yet
entry.timestampMs = entry.timestamp.toString()
entry.rects = entry.visibleLayers
.sort((a: any, b: any) => (b.absoluteZ > a.absoluteZ) ? 1 : (a.absoluteZ == b.absoluteZ) ? 0 : -1)
.map((it: any) => it.rect);
// Avoid parsing the entry root because it is an array of layers
// containing all trace information, this slows down the property tree.
// Instead parse only key properties for debugging
const entryIds: any = {}
protos.forEach((it: any) =>
entryIds[<keyof typeof entryIds>it.id] = `\nparent=${it.parent}\ntype=${it.type}\nname=${it.name}`
);
entry.proto = entryIds;
entry.shortName = entry.name;
entry.chips = [];
entry.isVisible = true;
}
function updateChildren(entry: LayerTraceEntry) {
entry.flattenedLayers.forEach((it: any) => {
if (it.isVisible) {
it.chips.push(VISIBLE_CHIP);
}
if (it.zOrderRelativeOf) {
it.chips.push(RELATIVE_Z_PARENT_CHIP);
}
if (it.isMissing) {
it.chips.push(MISSING_LAYER);
}
});
}
function newDisplay(proto: any): Display {
return new Display(
proto.id,
proto.name,
proto.layerStack,
toSize(proto.size),
toRect(proto.layerStackSpaceRect),
toTransform(proto.transform),
proto.isVirtual
)
}
export {LayerTraceEntry};

View File

@@ -0,0 +1,90 @@
/*
* Copyright 2021, 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 { Transform, Matrix33 } from "../common"
Transform.fromProto = function (transformProto: any, positionProto: any): Transform {
const entry = new Transform(
transformProto?.type ?? 0,
getMatrix(transformProto, positionProto))
return entry
}
function getMatrix(transform: any, position: any): Matrix33 {
const x = position?.x ?? 0
const y = position?.y ?? 0
if (transform == null || isSimpleTransform(transform.type)) {
return getDefaultTransform(transform?.type, x, y)
}
return new Matrix33(transform.dsdx, transform.dtdx, x, transform.dsdy, transform.dtdy, y)
}
function getDefaultTransform(type: number, x: number, y: number): Matrix33 {
// IDENTITY
if (!type) {
return new Matrix33(1, 0, x, 0, 1, y)
}
// ROT_270 = ROT_90|FLIP_H|FLIP_V
if (isFlagSet(type, ROT_90_VAL | FLIP_V_VAL | FLIP_H_VAL)) {
return new Matrix33(0, -1, x, 1, 0, y)
}
// ROT_180 = FLIP_H|FLIP_V
if (isFlagSet(type, FLIP_V_VAL | FLIP_H_VAL)) {
return new Matrix33(-1, 0, x, 0, -1, y)
}
// ROT_90
if (isFlagSet(type, ROT_90_VAL)) {
return new Matrix33(0, 1, x, -1, 0, y)
}
// IDENTITY
if (isFlagClear(type, SCALE_VAL | ROTATE_VAL)) {
return new Matrix33(1, 0, x, 0, 1, y)
}
throw new Error(`Unknown transform type ${type}`)
}
export function isFlagSet(type: number, bits: number): Boolean {
var type = type || 0;
return (type & bits) === bits;
}
export function isFlagClear(type: number, bits: number): Boolean {
return (type & bits) === 0;
}
export function isSimpleTransform(type: number): Boolean {
return isFlagClear(type, ROT_INVALID_VAL | SCALE_VAL)
}
/* transform type flags */
const ROTATE_VAL = 0x0002
const SCALE_VAL = 0x0004
/* orientation flags */
const FLIP_H_VAL = 0x0100 // (1 << 0 << 8)
const FLIP_V_VAL = 0x0200 // (1 << 1 << 8)
const ROT_90_VAL = 0x0400 // (1 << 2 << 8)
const ROT_INVALID_VAL = 0x8000 // (0x80 << 8)
export default Transform

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2020, 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 ObjectFormatter from "./ObjectFormatter"
/**
* Get the properties of a WM object for display.
*
* @param entry WM hierarchy element
* @param proto Associated proto object
*/
export function getPropertiesForDisplay(entry: any): any {
if (!entry) {
return
}
let obj: any = {}
const properties = ObjectFormatter.getProperties(entry)
properties.forEach(prop => obj[prop] = entry[prop]);
// we remove the children property from the object to avoid it showing the
// the properties view of the element as we can always see those elements'
// properties by changing the target element in the hierarchy tree view.
if (obj.children) delete obj.children
if (obj.proto) delete obj.proto
obj.proto = Object.assign({}, entry.proto)
if (obj.proto.children) delete obj.proto.children
if (obj.proto.childWindows) delete obj.proto.childWindows
if (obj.proto.childrenWindows) delete obj.proto.childrenWindows
if (obj.proto.childContainers) delete obj.proto.childContainers
if (obj.proto.windowToken) delete obj.proto.windowToken
if (obj.proto.rootDisplayArea) delete obj.proto.rootDisplayArea
if (obj.proto.rootWindowContainer) delete obj.proto.rootWindowContainer
if (obj.proto.windowContainer?.children) delete obj.proto.windowContainer.children
obj = ObjectFormatter.format(obj)
return obj
}
export function shortenName(name: any): string {
const classParts = (name + "").split(".")
if (classParts.length <= 3) {
return name
}
const className = classParts.slice(-1)[0] // last element
return `${classParts[0]}.${classParts[1]}.(...).${className}`
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2021, 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 { Tag } from "../common";
import TransitionType from "./TransitionType";
const transitionTypeMap = new Map([
['ROTATION', TransitionType.ROTATION],
['PIP_ENTER', TransitionType.PIP_ENTER],
['PIP_RESIZE', TransitionType.PIP_RESIZE],
['PIP_EXPAND', TransitionType.PIP_EXPAND],
['PIP_EXIT', TransitionType.PIP_EXIT],
['APP_LAUNCH', TransitionType.APP_LAUNCH],
['APP_CLOSE', TransitionType.APP_CLOSE],
['IME_APPEAR', TransitionType.IME_APPEAR],
['IME_DISAPPEAR', TransitionType.IME_DISAPPEAR],
['APP_PAIRS_ENTER', TransitionType.APP_PAIRS_ENTER],
['APP_PAIRS_EXIT', TransitionType.APP_PAIRS_EXIT],
]);
Tag.fromProto = function (proto: any): Tag {
const tag = new Tag(
proto.id,
transitionTypeMap.get(proto.transition),
proto.isStartTag,
proto.layerId,
proto.windowToken,
proto.taskId
);
return tag;
};
export default Tag;

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2020, 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 { TagState } from "../common";
import Tag from './Tag';
TagState.fromProto = function (timestamp: number, protos: any[]): TagState {
const tags = protos.map(it => Tag.fromProto(it));
const state = new TagState(`${timestamp}`, tags);
return state;
}
export default TagState;

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2021, 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.
*/
enum TransitionType {
ROTATION = 'ROTATION',
PIP_ENTER = 'PIP_ENTER',
PIP_RESIZE ='PIP_RESIZE',
PIP_EXPAND = 'PIP_EXPAND',
PIP_EXIT = 'PIP_EXIT',
APP_LAUNCH = 'APP_LAUNCH',
APP_CLOSE = 'APP_CLOSE',
IME_APPEAR = 'IME_APPEAR',
IME_DISAPPEAR = 'IME_DISAPPEAR',
APP_PAIRS_ENTER = 'APP_PAIRS_ENTER',
APP_PAIRS_EXIT = 'APP_PAIRS_EXIT',
};
export default TransitionType;

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2020, 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 ChipType from "./ChipType"
export default class Chip {
short: String
long: String
type: ChipType
constructor(short: String, long: String, type: ChipType) {
this.short = short
this.long = long
this.type = type
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2020, 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.
*/
enum ChipType {
DEFAULT = 'default'
}
export default ChipType

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2020, 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 Chip from "./Chip"
import ChipType from "./ChipType"
export const VISIBLE_CHIP = new Chip("V", "visible", ChipType.DEFAULT)
export const RELATIVE_Z_CHIP = {
short: 'RelZ',
long: 'Is relative Z-ordered to another surface',
class: 'warn',
};
export const RELATIVE_Z_PARENT_CHIP = {
short: 'RelZParent',
long: 'Something is relative Z-ordered to this surface',
class: 'warn',
};
export const MISSING_LAYER = {
short: 'MissingLayer',
long: 'This layer was referenced from the parent, but not present in the trace',
class: 'error',
};
export const GPU_CHIP = {
short: 'GPU',
long: 'This layer was composed on the GPU',
class: 'gpu',
};
export const HWC_CHIP = {
short: 'HWC',
long: 'This layer was composed by Hardware Composer',
class: 'hwc',
};

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2020, 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 { shortenName } from '../mixin'
import { Activity } from "../common"
import { VISIBLE_CHIP } from '../treeview/Chips'
import WindowContainer from "./WindowContainer"
Activity.fromProto = function (proto: any): Activity {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowToken.windowContainer,
/* protoChildren */ proto.windowToken.windowContainer?.children?.reverse() ?? [],
/* isActivityInTree */ true,
/* nameOverride */ null,
/* identifierOverride */ proto.identifier
);
const entry = new Activity(
proto.name,
proto.state,
proto.visible,
proto.frontOfTask,
proto.procId,
proto.translucent,
windowContainer
);
addAttributes(entry, proto);
return entry;
}
}
function addAttributes(entry: Activity, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
entry.chips = entry.isVisible ? [VISIBLE_CHIP] : [];
}
export default Activity;

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2020, 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 { shortenName } from '../mixin'
import { DisplayArea } from "../common"
import WindowContainer from "./WindowContainer"
DisplayArea.fromProto = function (proto: any, isActivityInTree: Boolean): DisplayArea {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children?.reverse() ?? [],
/* isActivityInTree */ isActivityInTree,
/* nameOverride */ proto.name
);
const entry = new DisplayArea(proto.isTaskDisplayArea, windowContainer);
addAttributes(entry, proto);
return entry;
}
}
function addAttributes(entry: DisplayArea, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
export default DisplayArea;

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2020, 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 { shortenName } from '../mixin'
import { toRect, DisplayContent, Rect } from "../common"
import WindowContainer from "./WindowContainer"
DisplayContent.fromProto = function (proto: any, isActivityInTree: Boolean): DisplayContent {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.rootDisplayArea.windowContainer,
/* protoChildren */ proto.rootDisplayArea.windowContainer?.children?.reverse() ?? [],
/* isActivityInTree */ isActivityInTree,
/* nameOverride */ proto.displayInfo?.name ?? null
);
const displayRectWidth = proto.displayInfo?.logicalWidth ?? 0;
const displayRectHeight = proto.displayInfo?.logicalHeight ?? 0;
const appRectWidth = proto.displayInfo?.appWidth ?? 0;
const appRectHeight = proto.displayInfo?.appHeight ?? 0;
const defaultBounds = proto.pinnedStackController?.defaultBounds ?? null;
const movementBounds = proto.pinnedStackController?.movementBounds ?? null;
const entry = new DisplayContent(
proto.id,
proto.focusedRootTaskId,
proto.resumedActivity?.title ?? "",
proto.singleTaskInstance,
toRect(defaultBounds),
toRect(movementBounds),
new Rect(0, 0, displayRectWidth, displayRectHeight),
new Rect(0, 0, appRectWidth, appRectHeight),
proto.dpi,
proto.displayInfo?.flags ?? 0,
toRect(proto.displayFrames?.stableBounds),
proto.surfaceSize,
proto.focusedApp,
proto.appTransition?.lastUsedAppTransition ?? "",
proto.appTransition?.appTransitionState ?? "",
proto.displayRotation?.rotation ?? 0,
proto.displayRotation?.lastOrientation ?? 0,
windowContainer
);
addAttributes(entry, proto);
return entry;
}
}
function addAttributes(entry: DisplayContent, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
export default DisplayContent;

View File

@@ -0,0 +1,64 @@
/*
* Copyright 2020, 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 { shortenName } from '../mixin'
import { Task, toRect } from "../common"
import WindowContainer from "./WindowContainer"
Task.fromProto = function (proto: any, isActivityInTree: Boolean): Task {
if (proto == null) {
return null;
} else {
const windowContainerProto = proto.taskFragment?.windowContainer ?? proto.windowContainer;
const windowContainer = WindowContainer.fromProto(
/* proto */ windowContainerProto,
/* protoChildren */ windowContainerProto?.children?.reverse() ?? [],
/* isActivityInTree */ isActivityInTree
);
const entry = new Task(
proto.taskFragment?.activityType ?? proto.activityType,
proto.fillsParent,
toRect(proto.bounds),
proto.id,
proto.rootTaskId,
proto.taskFragment?.displayId,
toRect(proto.lastNonFullscreenBounds),
proto.realActivity,
proto.origActivity,
proto.resizeMode,
proto.resumedActivity?.title ?? "",
proto.animatingBounds,
proto.surfaceWidth,
proto.surfaceHeight,
proto.createdByOrganizer,
proto.taskFragment?.minWidth ?? proto.minWidth,
proto.taskFragment?.minHeight ?? proto.minHeight,
windowContainer
);
addAttributes(entry, proto);
return entry;
}
}
function addAttributes(entry: Task, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
export default Task;

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2021, 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 { shortenName } from '../mixin'
import { TaskFragment } from "../common"
import WindowContainer from "./WindowContainer"
TaskFragment.fromProto = function (proto: any, isActivityInTree: Boolean): TaskFragment {
if (proto == null) {
return null;
} else {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children?.reverse() ?? [],
/* isActivityInTree */ isActivityInTree);
const entry = new TaskFragment(
proto.activityType,
proto.displayId,
proto.minWidth,
proto.minHeight,
windowContainer
);
addAttributes(entry, proto);
return entry;
}
}
function addAttributes(entry: TaskFragment, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
export default TaskFragment;

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2020, 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 { shortenName } from '../mixin'
import {
Configuration,
ConfigurationContainer,
toRect,
WindowConfiguration,
WindowContainer
} from "../common"
import Activity from "./Activity"
import DisplayArea from "./DisplayArea"
import DisplayContent from "./DisplayContent"
import Task from "./Task"
import TaskFragment from "./TaskFragment"
import WindowState from "./WindowState"
import WindowToken from "./WindowToken"
WindowContainer.fromProto = function (
proto: any,
protoChildren: any[],
isActivityInTree: boolean,
nameOverride: string|null = null,
identifierOverride: string|null = null,
tokenOverride: any = null,
): WindowContainer {
if (proto == null) {
return null;
}
const children = protoChildren
.filter(it => it != null)
.map(it => WindowContainer.childrenFromProto(it, isActivityInTree))
.filter(it => it != null);
const identifier: any = identifierOverride ?? proto.identifier;
const name: string = nameOverride ?? identifier?.title ?? "";
const token: string = tokenOverride?.toString(16) ?? identifier?.hashCode?.toString(16) ?? "";
const config = createConfigurationContainer(proto.configurationContainer);
const entry = new WindowContainer(
name,
token,
proto.orientation,
proto.surfaceControl?.layerId ?? 0,
proto.visible,
config,
children
);
addAttributes(entry, proto);
return entry;
}
function addAttributes(entry: WindowContainer, proto: any) {
entry.proto = proto;
entry.kind = entry.constructor.name;
entry.shortName = shortenName(entry.name);
}
type WindowContainerChildType = DisplayContent|DisplayArea|Task|TaskFragment|Activity|WindowToken|WindowState|WindowContainer;
WindowContainer.childrenFromProto = function(proto: any, isActivityInTree: Boolean): WindowContainerChildType {
return DisplayContent.fromProto(proto.displayContent, isActivityInTree) ??
DisplayArea.fromProto(proto.displayArea, isActivityInTree) ??
Task.fromProto(proto.task, isActivityInTree) ??
TaskFragment.fromProto(proto.taskFragment, isActivityInTree) ??
Activity.fromProto(proto.activity) ??
WindowToken.fromProto(proto.windowToken, isActivityInTree) ??
WindowState.fromProto(proto.window, isActivityInTree) ??
WindowContainer.fromProto(proto.windowContainer);
}
function createConfigurationContainer(proto: any): ConfigurationContainer {
const entry = new ConfigurationContainer(
createConfiguration(proto?.overrideConfiguration ?? null),
createConfiguration(proto?.fullConfiguration ?? null),
createConfiguration(proto?.mergedOverrideConfiguration ?? null)
);
entry.obj = entry;
return entry;
}
function createConfiguration(proto: any): Configuration {
if (proto == null) {
return null;
}
var windowConfiguration = null;
if (proto != null && proto.windowConfiguration != null) {
windowConfiguration = createWindowConfiguration(proto.windowConfiguration);
}
return new Configuration(
windowConfiguration,
proto?.densityDpi ?? 0,
proto?.orientation ?? 0,
proto?.screenHeightDp ?? 0,
proto?.screenHeightDp ?? 0,
proto?.smallestScreenWidthDp ?? 0,
proto?.screenLayout ?? 0,
proto?.uiMode ?? 0
);
}
function createWindowConfiguration(proto: any): WindowConfiguration {
return new WindowConfiguration(
toRect(proto.appBounds),
toRect(proto.bounds),
toRect(proto.maxBounds),
proto.windowingMode,
proto.activityType
);
}
export default WindowContainer;

View File

@@ -0,0 +1,117 @@
/*
* Copyright 2020, 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 {
KeyguardControllerState,
RootWindowContainer,
WindowManagerPolicy,
WindowManagerState
} from "../common"
import WindowContainer from "./WindowContainer"
WindowManagerState.fromProto = function (proto: any, timestamp: number = 0, where: string = ""): WindowManagerState {
var inputMethodWIndowAppToken = "";
if (proto.inputMethodWindow != null) {
proto.inputMethodWindow.hashCode.toString(16)
};
const rootWindowContainer = createRootWindowContainer(proto.rootWindowContainer);
const keyguardControllerState = createKeyguardControllerState(
proto.rootWindowContainer.keyguardController);
const policy = createWindowManagerPolicy(proto.policy);
const entry = new WindowManagerState(
where,
policy,
proto.focusedApp,
proto.focusedDisplayId,
proto.focusedWindow?.title ?? "",
inputMethodWIndowAppToken,
proto.rootWindowContainer.isHomeRecentsComponent,
proto.displayFrozen,
proto.rootWindowContainer.pendingActivities.map((it: any) => it.title),
rootWindowContainer,
keyguardControllerState,
/*timestamp */ `${timestamp}`
);
addAttributes(entry, proto);
return entry
}
function addAttributes(entry: WindowManagerState, proto: any) {
entry.kind = entry.constructor.name;
// There no JVM/JS translation for Longs yet
entry.timestampMs = entry.timestamp.toString();
entry.rects = entry.windowStates.reverse().map((it: any) => it.rect);
if (!entry.isComplete()) {
entry.isIncompleteReason = entry.getIsIncompleteReason();
}
entry.proto = proto;
entry.shortName = entry.name;
entry.chips = [];
entry.isVisible = true;
}
function createWindowManagerPolicy(proto: any): WindowManagerPolicy {
return new WindowManagerPolicy(
proto.focusedAppToken ?? "",
proto.forceStatusBar,
proto.forceStatusBarFromKeyguard,
proto.keyguardDrawComplete,
proto.keyguardOccluded,
proto.keyguardOccludedChanged,
proto.keyguardOccludedPending,
proto.lastSystemUiFlags,
proto.orientation,
proto.rotation,
proto.rotationMode,
proto.screenOnFully,
proto.windowManagerDrawComplete
);
}
function createRootWindowContainer(proto: any): RootWindowContainer {
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* childrenProto */ proto.windowContainer?.children?.reverse() ?? [],
/* isActivityInTree */ false
);
if (windowContainer == null) {
throw new Error(`Window container should not be null.\n${JSON.stringify(proto)}`);
}
const entry = new RootWindowContainer(windowContainer);
return entry;
}
function createKeyguardControllerState(proto: any): KeyguardControllerState {
const keyguardOccludedStates: any = {};
if (proto) {
proto.keyguardOccludedStates.forEach((it: any) =>
keyguardOccludedStates[<keyof typeof keyguardOccludedStates>it.displayId] = it.keyguardOccluded);
}
return new KeyguardControllerState(
proto?.isAodShowing ?? false,
proto?.isKeyguardShowing ?? false,
keyguardOccludedStates
);
}
export {WindowManagerState};

View File

@@ -0,0 +1,137 @@
/*
* Copyright 2020, 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 { shortenName } from '../mixin'
import { toRect, Size, WindowState, WindowLayoutParams } from "../common"
import { VISIBLE_CHIP } from '../treeview/Chips'
import WindowContainer from "./WindowContainer"
WindowState.fromProto = function (proto: any, isActivityInTree: Boolean): WindowState {
if (proto == null) {
return null;
} else {
const windowParams = createWindowLayoutParams(proto.attributes);
const identifierName = getIdentifier(proto);
const windowType = getWindowType(proto, identifierName);
const name = getName(identifierName);
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children.reverse() ?? [],
/* isActivityInTree */ isActivityInTree,
/* nameOverride */ name,
/* identifierOverride */ proto.identifier
);
const entry = new WindowState(
windowParams,
proto.displayId,
proto.stackId,
proto.animator?.surface?.layer ?? 0,
proto.animator?.surface?.shown ?? false,
windowType,
new Size(proto.requestedWidth, proto.requestedHeight),
toRect(proto.surfacePosition),
toRect(proto.windowFrames?.frame ?? null),
toRect(proto.windowFrames?.containingFrame ?? null),
toRect(proto.windowFrames?.parentFrame ?? null),
toRect(proto.windowFrames?.contentFrame ?? null),
toRect(proto.windowFrames?.contentInsets ?? null),
toRect(proto.surfaceInsets),
toRect(proto.givenContentInsets),
toRect(proto.animator?.lastClipRect ?? null),
windowContainer,
/* isAppWindow */ isActivityInTree
);
addAttributes(entry, proto);
return entry;
}
}
function createWindowLayoutParams(proto: any): WindowLayoutParams {
return new WindowLayoutParams(
/* type */ proto?.type ?? 0,
/* x */ proto?.x ?? 0,
/* y */ proto?.y ?? 0,
/* width */ proto?.width ?? 0,
/* height */ proto?.height ?? 0,
/* horizontalMargin */ proto?.horizontalMargin ?? 0,
/* verticalMargin */ proto?.verticalMargin ?? 0,
/* gravity */ proto?.gravity ?? 0,
/* softInputMode */ proto?.softInputMode ?? 0,
/* format */ proto?.format ?? 0,
/* windowAnimations */ proto?.windowAnimations ?? 0,
/* alpha */ proto?.alpha ?? 0,
/* screenBrightness */ proto?.screenBrightness ?? 0,
/* buttonBrightness */ proto?.buttonBrightness ?? 0,
/* rotationAnimation */ proto?.rotationAnimation ?? 0,
/* preferredRefreshRate */ proto?.preferredRefreshRate ?? 0,
/* preferredDisplayModeId */ proto?.preferredDisplayModeId ?? 0,
/* hasSystemUiListeners */ proto?.hasSystemUiListeners ?? false,
/* inputFeatureFlags */ proto?.inputFeatureFlags ?? 0,
/* userActivityTimeout */ proto?.userActivityTimeout ?? 0,
/* colorMode */ proto?.colorMode ?? 0,
/* flags */ proto?.flags ?? 0,
/* privateFlags */ proto?.privateFlags ?? 0,
/* systemUiVisibilityFlags */ proto?.systemUiVisibilityFlags ?? 0,
/* subtreeSystemUiVisibilityFlags */ proto?.subtreeSystemUiVisibilityFlags ?? 0,
/* appearance */ proto?.appearance ?? 0,
/* behavior */ proto?.behavior ?? 0,
/* fitInsetsTypes */ proto?.fitInsetsTypes ?? 0,
/* fitInsetsSides */ proto?.fitInsetsSides ?? 0,
/* fitIgnoreVisibility */ proto?.fitIgnoreVisibility ?? false
)
}
function getWindowType(proto: any, identifierName: string): number {
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
return WindowState.WINDOW_TYPE_STARTING;
} else if (proto.animatingExit) {
return WindowState.WINDOW_TYPE_EXITING;
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
return WindowState.WINDOW_TYPE_STARTING;
}
return 0;
}
function getName(identifierName: string): string {
var name = identifierName;
if (identifierName.startsWith(WindowState.STARTING_WINDOW_PREFIX)) {
name = identifierName.substring(WindowState.STARTING_WINDOW_PREFIX.length);
} else if (identifierName.startsWith(WindowState.DEBUGGER_WINDOW_PREFIX)) {
name = identifierName.substring(WindowState.DEBUGGER_WINDOW_PREFIX.length);
}
return name;
}
function getIdentifier(proto: any): string {
return proto.windowContainer.identifier?.title ?? proto.identifier?.title ?? "";
}
function addAttributes(entry: WindowState, proto: any) {
entry.kind = entry.constructor.name;
entry.rect = entry.frame;
entry.rect.ref = entry;
entry.rect.label = entry.name;
entry.proto = proto;
entry.shortName = shortenName(entry.name);
entry.chips = entry.isVisible ? [VISIBLE_CHIP] : [];
}
export default WindowState

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2020, 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 { shortenName } from '../mixin'
import { WindowToken } from "../common"
import WindowContainer from "./WindowContainer"
WindowToken.fromProto = function (proto: any, isActivityInTree: Boolean): WindowToken {
if (proto == null) {
return null;
}
const windowContainer = WindowContainer.fromProto(
/* proto */ proto.windowContainer,
/* protoChildren */ proto.windowContainer?.children?.reverse() ?? [],
/* isActivityInTree */ isActivityInTree,
/* nameOverride */ null,
/* identifierOverride */ null,
/* tokenOverride */ proto.hashCode
);
const entry = new WindowToken(windowContainer);
entry.kind = entry.constructor.name;
entry.proto = proto;
entry.shortName = shortenName(entry.name);
return entry;
}
export default WindowToken;

View File

@@ -0,0 +1,136 @@
/*
* 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 {StringUtils} from "common/utils/string_utils"
import configJson from "../../../../../../frameworks/base/data/etc/services.core.protolog.json";
class LogMessage {
text: string;
time: string;
tag: string;
level: string;
at: string;
timestamp: number;
constructor(text: string, time: string, tag: string, level: string, at: string, timestamp: number) {
this.text = text;
this.time = time;
this.tag = tag;
this.level = level;
this.at = at;
this.timestamp = timestamp;
}
}
class FormattedLogMessage extends LogMessage {
constructor(proto: any) {
const text = (
proto.messageHash.toString() +
' - [' + proto.strParams.toString() +
'] [' + proto.sint64Params.toString() +
'] [' + proto.doubleParams.toString() +
'] [' + proto.booleanParams.toString() + ']'
);
super(
text,
StringUtils.nanosecondsToHuman(proto.elapsedRealtimeNanos),
'INVALID',
'invalid',
'',
Number(proto.elapsedRealtimeNanos));
}
}
class UnformattedLogMessage extends LogMessage {
constructor(proto: any, message: any) {
super(
formatText(message.message, proto),
StringUtils.nanosecondsToHuman(proto.elapsedRealtimeNanos),
(<any>configJson).groups[message.group].tag,
message.level,
message.at,
Number(proto.elapsedRealtimeNanos)
);
}
}
function formatText(messageFormat: any, data: any) {
let out = '';
const strParams: string[] = data.strParams;
let strParamsIdx = 0;
const sint64Params: number[] = data.sint64Params;
let sint64ParamsIdx = 0;
const doubleParams: number[] = data.doubleParams;
let doubleParamsIdx = 0;
const booleanParams: number[] = data.booleanParams;
let booleanParamsIdx = 0;
for (let i = 0; i < messageFormat.length;) {
if (messageFormat[i] == '%') {
if (i + 1 >= messageFormat.length) {
// Should never happen - protologtool checks for that
throw new Error('Invalid format string');
}
switch (messageFormat[i + 1]) {
case '%':
out += '%';
break;
case 'd':
out += getParam(sint64Params, sint64ParamsIdx++).toString(10);
break;
case 'o':
out += getParam(sint64Params, sint64ParamsIdx++).toString(8);
break;
case 'x':
out += getParam(sint64Params, sint64ParamsIdx++).toString(16);
break;
case 'f':
out += getParam(doubleParams, doubleParamsIdx++).toFixed(6);
break;
case 'e':
out += getParam(doubleParams, doubleParamsIdx++).toExponential();
break;
case 'g':
out += getParam(doubleParams, doubleParamsIdx++).toString();
break;
case 's':
out += getParam(strParams, strParamsIdx++);
break;
case 'b':
out += getParam(booleanParams, booleanParamsIdx++).toString();
break;
default:
// Should never happen - protologtool checks for that
throw new Error('Invalid format string conversion: ' +
messageFormat[i + 1]);
}
i += 2;
} else {
out += messageFormat[i];
i += 1;
}
}
return out;
}
function getParam<T>(arr: T[], idx: number): T {
if (arr.length <= idx) {
throw new Error('No param for format string conversion');
}
return arr[idx];
}
export {FormattedLogMessage, LogMessage, UnformattedLogMessage};

View File

@@ -0,0 +1,37 @@
/*
* 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.
*/
enum TraceTypeId {
ACCESSIBILITY,
WINDOW_MANAGER,
SURFACE_FLINGER,
WINDOW_MANAGER_DUMP,
SURFACE_FLINGER_DUMP,
SCREEN_RECORDING,
TRANSACTIONS,
TRANSACTIONS_LEGACY,
WAYLAND,
WAYLAND_DUMP,
PROTO_LOG,
SYSTEM_UI,
LAUNCHER,
INPUT_METHOD_CLIENTS,
INPUT_METHOD_MANAGER_SERVICE,
INPUT_METHOD_SERVICE,
TAG,
ERROR,
};
export {TraceTypeId};

View File

@@ -0,0 +1,62 @@
/*
* 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 {ArrayUtils} from './array_utils';
describe("ArrayUtils", () => {
it("equal", () => {
expect(ArrayUtils.equal([], [1])).toBeFalse();
expect(ArrayUtils.equal([1], [])).toBeFalse();
expect(ArrayUtils.equal([], [])).toBeTrue();
expect(ArrayUtils.equal([undefined], [undefined])).toBeTrue();
expect(ArrayUtils.equal([1, 2, 3], [1, 2, 3])).toBeTrue();
expect(ArrayUtils.equal([], new Uint8Array(1))).toBeFalse();
expect(ArrayUtils.equal([1], new Uint8Array(1))).toBeFalse();
expect(ArrayUtils.equal([], new Uint8Array())).toBeTrue();
expect(ArrayUtils.equal([], new Uint8Array())).toBeTrue();
expect(ArrayUtils.equal([1, 2, 3], new Uint8Array([1, 2, 3]))).toBeTrue();
expect(ArrayUtils.equal(new Uint8Array([]), new Uint8Array([1]))).toBeFalse();
expect(ArrayUtils.equal(new Uint8Array([1]), new Uint8Array([]))).toBeFalse();
expect(ArrayUtils.equal(new Uint8Array([]), new Uint8Array([]))).toBeTrue();
expect(ArrayUtils.equal(new Uint8Array([1, 2, 3]), new Uint8Array([1, 2, 3]))).toBeTrue();
});
it("binarySearchLowerOrEqual", () => {
// no match
expect(ArrayUtils.binarySearchLowerOrEqual([], 5)).toBeUndefined();
expect(ArrayUtils.binarySearchLowerOrEqual([6], 5)).toBeUndefined();
expect(ArrayUtils.binarySearchLowerOrEqual([6, 7], 5)).toBeUndefined();
expect(ArrayUtils.binarySearchLowerOrEqual([6, 7, 8], 5)).toBeUndefined();
// match (lower)
expect(ArrayUtils.binarySearchLowerOrEqual([4], 5)).toEqual(0);
expect(ArrayUtils.binarySearchLowerOrEqual([3, 4], 5)).toEqual(1);
expect(ArrayUtils.binarySearchLowerOrEqual([2, 3, 4], 5)).toEqual(2);
expect(ArrayUtils.binarySearchLowerOrEqual([2, 3, 4, 6], 5)).toEqual(2);
expect(ArrayUtils.binarySearchLowerOrEqual([2, 3, 4, 6, 7], 5)).toEqual(2);
// match (equal)
expect(ArrayUtils.binarySearchLowerOrEqual([5], 5)).toEqual(0);
expect(ArrayUtils.binarySearchLowerOrEqual([4, 5], 5)).toEqual(1);
expect(ArrayUtils.binarySearchLowerOrEqual([3, 4, 5], 5)).toEqual(2);
expect(ArrayUtils.binarySearchLowerOrEqual([3, 4, 5, 6], 5)).toEqual(2);
expect(ArrayUtils.binarySearchLowerOrEqual([3, 4, 5, 6, 7], 5)).toEqual(2);
});
});

View File

@@ -0,0 +1,71 @@
/*
* 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.
*/
type TypedArray =
| Int8Array
| Uint8Array
| Uint8ClampedArray
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Float32Array
| Float64Array;
class ArrayUtils {
static equal<T>(a: T[] | TypedArray, b: T[] | TypedArray): boolean {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
static binarySearchLowerOrEqual<T>(values: T[] | TypedArray, target: T): number|undefined {
if (values.length == 0) {
return undefined;
}
let low = 0;
let high = values.length - 1;
let result: number|undefined = undefined;
while(low <= high) {
const mid = (low + high) >> 1;
if (values[mid] < target) {
result = mid;
low = mid + 1;
}
else if (values[mid] > target) {
high = mid - 1;
}
else {
return mid;
}
}
return result;
}
}
export {ArrayUtils};

View File

@@ -0,0 +1,48 @@
/*
* 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 {StringUtils} from './string_utils';
describe("StringUtils", () => {
it("nanosecondsToHuman", () => {
const MILLISECOND = 1000000;
const SECOND = 1000000000;
const MINUTE = 60 * SECOND;
const HOUR = 60 * MINUTE;
const DAY = 24 * HOUR;
expect(StringUtils.nanosecondsToHuman(0)) .toEqual("0ms");
expect(StringUtils.nanosecondsToHuman(1000)) .toEqual("0ms");
expect(StringUtils.nanosecondsToHuman(MILLISECOND-1)).toEqual("0ms");
expect(StringUtils.nanosecondsToHuman(MILLISECOND)).toEqual("1ms");
expect(StringUtils.nanosecondsToHuman(10 * MILLISECOND)).toEqual("10ms");
expect(StringUtils.nanosecondsToHuman(SECOND-1)).toEqual("999ms");
expect(StringUtils.nanosecondsToHuman(SECOND)).toEqual("1s0ms");
expect(StringUtils.nanosecondsToHuman(SECOND + MILLISECOND)).toEqual("1s1ms");
expect(StringUtils.nanosecondsToHuman(MINUTE-1)).toEqual("59s999ms");
expect(StringUtils.nanosecondsToHuman(MINUTE)).toEqual("1m0s0ms");
expect(StringUtils.nanosecondsToHuman(MINUTE + SECOND + MILLISECOND)).toEqual("1m1s1ms");
expect(StringUtils.nanosecondsToHuman(HOUR-1)).toEqual("59m59s999ms");
expect(StringUtils.nanosecondsToHuman(HOUR)).toEqual("1h0m0s0ms");
expect(StringUtils.nanosecondsToHuman(HOUR + MINUTE + SECOND + MILLISECOND)).toEqual("1h1m1s1ms");
expect(StringUtils.nanosecondsToHuman(DAY-1)).toEqual("23h59m59s999ms");
expect(StringUtils.nanosecondsToHuman(DAY)).toEqual("1d0h0m0s0ms");
expect(StringUtils.nanosecondsToHuman(DAY + HOUR + MINUTE + SECOND + MILLISECOND)).toEqual("1d1h1m1s1ms");
});
});

View File

@@ -0,0 +1,43 @@
/*
* 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 StringUtils {
static nanosecondsToHuman(nanoseconds: number): string {
const units: [number, string][] = [
[1000, "ms"],
[60, "s"],
[60, "m"],
[24, "h"],
[Infinity, "d"],
];
let remainder = Math.floor(nanoseconds / 1000000);
const parts = [];
for(const [factor, unit] of units) {
const part = (remainder % factor).toFixed();
parts.push(part + unit);
remainder = Math.floor(remainder / factor);
if (remainder == 0) {
break;
}
}
return parts.reverse().join('');
}
}
export {StringUtils};

Binary file not shown.

After

Width:  |  Height:  |  Size: 948 B

View File

@@ -0,0 +1,28 @@
<!--
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.
-->
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>WinscopeNg</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@@ -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 'zone.js';
import 'zone.js/testing';
import {TestBed} from '@angular/core/testing';
import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
<T>(id: string): T;
keys(): string[];
};
};
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting(),
);
// load all tests of Angular components
const context = require.context('./', true, /\.component\.spec\.ts$/);
context.keys().forEach(context);

View File

@@ -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.
*/
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
//TODO: implement production mode switch
//enableProdMode();
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));

View File

@@ -0,0 +1,54 @@
/*
* 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 {ArrayUtils} from '../common/utils/array_utils';
import {TraceTypeId} from "common/trace/type_id";
abstract class Parser {
constructor(buffer: Uint8Array) {
const magicNumber = this.getMagicNumber();
const bufferContainsMagicNumber = ArrayUtils.equal(magicNumber, buffer.slice(0, magicNumber.length));
if (!bufferContainsMagicNumber) {
throw TypeError("buffer doesn't contain expected magic number");
}
this.traceEntriesProto = this.decodeProto(buffer);
this.timestamps = this.traceEntriesProto.map((entryProto: any) => this.getTimestamp(entryProto));
}
public abstract getTraceTypeId(): TraceTypeId;
public getTimestamps(): number[] {
return this.timestamps;
}
public getTraceEntry(timestamp: number): any|undefined {
const index = ArrayUtils.binarySearchLowerOrEqual(this.getTimestamps(), timestamp);
if (index === undefined) {
return undefined;
}
return this.processTraceEntryProto(this.traceEntriesProto[index]);
}
protected abstract getMagicNumber(): number[];
protected abstract decodeProto(buffer: Uint8Array): any[];
protected abstract getTimestamp(entryProto: any): number;
protected abstract processTraceEntryProto(entryProto: any): any;
private traceEntriesProto: any[];
private timestamps: number[];
}
export {Parser};

View File

@@ -0,0 +1,44 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Parser} from "./parser"
import {ParserFactory} from './parser_factory';
import {TestUtils} from "test/test_utils";
describe("ParserAccessibility", () => {
let parser: Parser;
beforeAll(() => {
const buffer = TestUtils.loadFixture("trace_Accessibility.pb");
const parsers = new ParserFactory().createParsers([buffer]);
expect(parsers.length).toEqual(1);
parser = parsers[0];
});
it("has expected trace type", () => {
expect(parser.getTraceTypeId()).toEqual(TraceTypeId.ACCESSIBILITY);
});
it("provides timestamps", () => {
expect(parser.getTimestamps())
.toEqual([850297444302, 850297882046, 850756176154, 850773581835]);
});
it("retrieves trace entry", () => {
expect(Number(parser.getTraceEntry(850297444302)!.elapsedRealtimeNanos))
.toEqual(850297444302);
});
});

View File

@@ -0,0 +1,48 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Parser} from './parser'
import {AccessibilityTraceFileProto} from './proto_types';
class ParserAccessibility extends Parser {
constructor(buffer: Uint8Array) {
super(buffer);
}
override getTraceTypeId(): TraceTypeId {
return TraceTypeId.ACCESSIBILITY;
}
override getMagicNumber(): number[] {
return ParserAccessibility.MAGIC_NUMBER;
}
override decodeProto(buffer: Uint8Array): any[] {
return (<any>AccessibilityTraceFileProto.decode(buffer)).entry;
}
override getTimestamp(entryProto: any): number {
return Number(entryProto.elapsedRealtimeNanos);
}
override processTraceEntryProto(entryProto: any): any {
return entryProto;
}
private static readonly MAGIC_NUMBER = [0x09, 0x41, 0x31, 0x31, 0x59, 0x54, 0x52, 0x41, 0x43]; // .A11YTRAC
}
export {ParserAccessibility};

View File

@@ -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 {TraceTypeId} from "common/trace/type_id";
import {ParserFactory} from "./parser_factory";
import {Parser} from "./parser";
import {TestUtils} from "test/test_utils";
describe("Parser", () => {
let parser: Parser;
beforeAll(() => {
const buffer = TestUtils.loadFixture("trace_WindowManager.pb");
const parsers = new ParserFactory().createParsers([buffer]);
expect(parsers.length).toEqual(1);
parser = parsers[0];
});
it("provides timestamps", () => {
expect(parser.getTimestamps())
.toEqual([850254319343, 850763506110, 850782750048]);
});
it("retrieves trace entry (no timestamp matches)", () => {
expect(parser.getTraceEntry(850254319342))
.toEqual(undefined);
});
it("retrieves trace entry (equal timestamp matches)", () => {
expect(Number(parser.getTraceEntry(850254319343)!.timestampMs))
.toEqual(850254319343);
});
it("retrieves trace entry (equal timestamp matches)", () => {
expect(Number(parser.getTraceEntry(850763506110)!.timestampMs))
.toEqual(850763506110);
});
it("retrieves trace entry (lower timestamp matches)", () => {
expect(Number(parser.getTraceEntry(850254319344)!.timestampMs))
.toEqual(850254319343);
});
it("retrieves trace entry (equal timestamp matches)", () => {
expect(Number(parser.getTraceEntry(850763506111)!.timestampMs))
.toEqual(850763506110);
});
});

View File

@@ -0,0 +1,56 @@
/*
* 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 {Parser} from "./parser";
import {ParserAccessibility} from "./parser_accessibility";
import {ParserInputMethodClients} from "./parser_input_method_clients";
import {ParserInputMethodManagerService} from "./parser_input_method_manager_service";
import {ParserInputMethodService} from "./parser_input_method_service";
import {ParserProtoLog} from "./parser_protolog"
import {ParserSurfaceFlinger} from "./parser_surface_flinger"
import {ParserTransactions} from "./parser_transactions";
import {ParserWindowManager} from "./parser_window_manager"
class ParserFactory {
static readonly PARSERS = [
ParserAccessibility,
ParserInputMethodClients,
ParserInputMethodManagerService,
ParserInputMethodService,
ParserProtoLog,
ParserSurfaceFlinger,
ParserTransactions,
ParserWindowManager,
]
createParsers(buffers: Uint8Array[]): Parser[] {
const parsers: Parser[] = [];
for (const buffer of buffers) {
for (const ParserType of ParserFactory.PARSERS) {
try {
const parser = new ParserType(buffer);
parsers.push(parser);
break;
} catch(error) {
}
}
}
return parsers;
}
}
export {ParserFactory};

View File

@@ -0,0 +1,46 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {ParserFactory} from "./parser_factory";
import {Parser} from "./parser";
import {TestUtils} from "test/test_utils";
describe("ParserInputMethodlClients", () => {
let parser: Parser;
beforeAll(() => {
const buffer = TestUtils.loadFixture("trace_InputMethodClients.pb");
const parsers = new ParserFactory().createParsers([buffer]);
expect(parsers.length).toEqual(1);
parser = parsers[0];
});
it("has expected trace type", () => {
expect(parser.getTraceTypeId()).toEqual(TraceTypeId.INPUT_METHOD_CLIENTS);
});
it("provides timestamps", () => {
expect(parser.getTimestamps().length)
.toEqual(33);
expect(parser.getTimestamps().slice(0, 3))
.toEqual([1149083651642, 1149083950633, 1149127567251]);
});
it("retrieves trace entry", () => {
expect(Number(parser.getTraceEntry(1149083651642)!.elapsedRealtimeNanos))
.toEqual(1149083651642);
});
});

View File

@@ -0,0 +1,48 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Parser} from './parser'
import {InputMethodClientsTraceFileProto} from './proto_types';
class ParserInputMethodClients extends Parser {
constructor(buffer: Uint8Array) {
super(buffer);
}
getTraceTypeId(): TraceTypeId {
return TraceTypeId.INPUT_METHOD_CLIENTS;
}
override getMagicNumber(): number[] {
return ParserInputMethodClients.MAGIC_NUMBER;
}
override decodeProto(buffer: Uint8Array): any[] {
return (<any>InputMethodClientsTraceFileProto.decode(buffer)).entry;
}
override getTimestamp(entryProto: any): number {
return Number(entryProto.elapsedRealtimeNanos);
}
override processTraceEntryProto(entryProto: any): any {
return entryProto;
}
private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x43, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMCTRACE
}
export {ParserInputMethodClients};

View File

@@ -0,0 +1,44 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {ParserFactory} from "./parser_factory";
import {Parser} from "./parser";
import {TestUtils} from "test/test_utils";
describe("ParserInputMethodManagerService", () => {
let parser: Parser;
beforeAll(() => {
const buffer = TestUtils.loadFixture("trace_InputMethodManagerService.pb");
const parsers = new ParserFactory().createParsers([buffer]);
expect(parsers.length).toEqual(1);
parser = parsers[0];
});
it("has expected trace type", () => {
expect(parser.getTraceTypeId()).toEqual(TraceTypeId.INPUT_METHOD_MANAGER_SERVICE);
});
it("provides timestamps", () => {
expect(parser.getTimestamps())
.toEqual([1149226290110, 1149237707591, 1149238950389]);
});
it("retrieves trace entry", () => {
expect(Number(parser.getTraceEntry(1149226290110)!.elapsedRealtimeNanos))
.toEqual(1149226290110);
});
});

View File

@@ -0,0 +1,48 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Parser} from './parser'
import {InputMethodManagerServiceTraceFileProto} from './proto_types';
class ParserInputMethodManagerService extends Parser {
constructor(buffer: Uint8Array) {
super(buffer);
}
getTraceTypeId(): TraceTypeId {
return TraceTypeId.INPUT_METHOD_MANAGER_SERVICE;
}
override getMagicNumber(): number[] {
return ParserInputMethodManagerService.MAGIC_NUMBER;
}
override decodeProto(buffer: Uint8Array): any[] {
return (<any>InputMethodManagerServiceTraceFileProto.decode(buffer)).entry;
}
protected override getTimestamp(entryProto: any): number {
return Number(entryProto.elapsedRealtimeNanos);
}
protected override processTraceEntryProto(entryProto: any): any {
return entryProto;
}
private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x4d, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMMTRACE
}
export {ParserInputMethodManagerService};

View File

@@ -0,0 +1,44 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {ParserFactory} from "./parser_factory";
import {Parser} from "./parser";
import {TestUtils} from "test/test_utils";
describe("ParserInputMethodService", () => {
let parser: Parser;
beforeAll(() => {
const buffer = TestUtils.loadFixture("trace_InputMethodService.pb");
const parsers = new ParserFactory().createParsers([buffer]);
expect(parsers.length).toEqual(1);
parser = parsers[0];
});
it("has expected trace type", () => {
expect(parser.getTraceTypeId()).toEqual(TraceTypeId.INPUT_METHOD_SERVICE);
});
it("provides timestamps", () => {
expect(parser.getTimestamps())
.toEqual([1149230019887, 1149234359324, 1149241227244, 1149243083608, 1149249518016, 1149249784617, 1149272993520]);
});
it("retrieves trace entry", () => {
expect(Number(parser.getTraceEntry(1149230019887)!.elapsedRealtimeNanos))
.toEqual(1149230019887);
});
});

View File

@@ -0,0 +1,48 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Parser} from './parser'
import {InputMethodServiceTraceFileProto} from './proto_types';
class ParserInputMethodService extends Parser {
constructor(buffer: Uint8Array) {
super(buffer);
}
getTraceTypeId(): TraceTypeId {
return TraceTypeId.INPUT_METHOD_SERVICE;
}
override getMagicNumber(): number[] {
return ParserInputMethodService.MAGIC_NUMBER;
}
override decodeProto(buffer: Uint8Array): any[] {
return (<any>InputMethodServiceTraceFileProto.decode(buffer)).entry;
}
override getTimestamp(entryProto: any): number {
return Number(entryProto.elapsedRealtimeNanos);
}
override processTraceEntryProto(entryProto: any): any {
return entryProto;
}
private static readonly MAGIC_NUMBER = [0x09, 0x49, 0x4d, 0x53, 0x54, 0x52, 0x41, 0x43, 0x45]; // .IMSTRACE
}
export {ParserInputMethodService};

View File

@@ -0,0 +1,55 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {ParserFactory} from "./parser_factory";
import {Parser} from "./parser";
import {TestUtils} from "test/test_utils";
describe("ParserProtoLog", () => {
let parser: Parser;
beforeAll(() => {
const buffer = TestUtils.loadFixture("trace_ProtoLog.pb");
const parsers = new ParserFactory().createParsers([buffer]);
expect(parsers.length).toEqual(1);
parser = parsers[0];
});
it("has expected trace type", () => {
expect(parser.getTraceTypeId()).toEqual(TraceTypeId.PROTO_LOG);
});
it("provides timestamps", () => {
const timestamps = parser.getTimestamps();
expect(timestamps.length)
.toEqual(50);
expect(timestamps.slice(0, 3))
.toEqual([850746266486, 850746336718, 850746350430]);
});
it("reconstructs human-readable log message", () => {
const actual = parser.getTraceEntry(850746266486)!;
const expected = {
text: "InsetsSource updateVisibility for ITYPE_IME, serverVisible: false clientVisible: false",
time: "14m10s746ms",
tag: "WindowManager",
level: "DEBUG",
at: "com/android/server/wm/InsetsSourceProvider.java",
timestamp: Number(850746266486),
};
expect(Object.assign({}, actual)).toEqual(expected);
});
});

View File

@@ -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 {LogMessage, FormattedLogMessage, UnformattedLogMessage} from "common/trace/protolog";
import {TraceTypeId} from "common/trace/type_id";
import {Parser} from "./parser"
import {ProtoLogFileProto} from "./proto_types";
import configJson from "../../../../../frameworks/base/data/etc/services.core.protolog.json";
class ParserProtoLog extends Parser {
constructor(buffer: Uint8Array) {
super(buffer);
}
override getTraceTypeId(): TraceTypeId {
return TraceTypeId.PROTO_LOG;
}
override getMagicNumber(): number[] {
return ParserProtoLog.MAGIC_NUMBER;
}
override decodeProto(buffer: Uint8Array): any[] {
const fileProto: any = ProtoLogFileProto.decode(buffer);
if (fileProto.version !== ParserProtoLog.PROTOLOG_VERSION) {
const message = "Unsupported ProtoLog trace version";
console.log(message);
throw new TypeError(message);
}
if (configJson.version !== ParserProtoLog.PROTOLOG_VERSION) {
const message = "Unsupported ProtoLog JSON config version";
console.log(message);
throw new TypeError(message);
}
fileProto.log.sort((a: any, b: any) => {
return Number(a.elapsedRealtimeNanos) - Number(b.elapsedRealtimeNanos);
});
return fileProto.log;
}
override getTimestamp(entryProto: any): number {
return Number(entryProto.elapsedRealtimeNanos);
}
override processTraceEntryProto(entryProto: any): LogMessage {
const message = (<any>configJson).messages[entryProto.messageHash];
if (!message) {
return new FormattedLogMessage(entryProto);
}
try {
return new UnformattedLogMessage(entryProto, message);
}
catch (error) {
if (error instanceof FormatStringMismatchError) {
return new FormattedLogMessage(entryProto);
}
throw error;
}
}
private static readonly MAGIC_NUMBER = [0x09, 0x50, 0x52, 0x4f, 0x54, 0x4f, 0x4c, 0x4f, 0x47]; // .PROTOLOG
private static readonly PROTOLOG_VERSION = "1.0.0";
}
class FormatStringMismatchError extends Error {
constructor(message: string) {
super(message);
}
}
export {ParserProtoLog};

View File

@@ -0,0 +1,46 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {LayerTraceEntry} from 'common/trace/flickerlib/layers/LayerTraceEntry';
import {TestUtils} from 'test/test_utils';
import {Parser} from './parser';
import {ParserFactory} from './parser_factory';
describe("ParserSurfaceFlinger", () => {
let parser: Parser;
beforeAll(() => {
const buffer = TestUtils.loadFixture("trace_SurfaceFlinger.pb");
const parsers = new ParserFactory().createParsers([buffer]);
expect(parsers.length).toEqual(1);
parser = parsers[0];
});
it("has expected trace type", () => {
expect(parser.getTraceTypeId()).toEqual(TraceTypeId.SURFACE_FLINGER);
});
it("provides timestamps", () => {
expect(parser.getTimestamps())
.toEqual([850335483446, 850686322883, 850736507697]);
});
it("retrieves trace entry", () => {
const entry = parser.getTraceEntry(850335483446)!
expect(entry).toBeInstanceOf(LayerTraceEntry);
expect(Number(entry.timestampMs)).toEqual(850335483446);
});
});

View File

@@ -0,0 +1,49 @@
/*
* 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 {LayerTraceEntry} from 'common/trace/flickerlib/layers/LayerTraceEntry';
import {TraceTypeId} from "common/trace/type_id";
import {Parser} from './parser'
import {LayersTraceFileProto} from './proto_types';
class ParserSurfaceFlinger extends Parser {
constructor(buffer: Uint8Array) {
super(buffer);
}
override getTraceTypeId(): TraceTypeId {
return TraceTypeId.SURFACE_FLINGER;
}
override getMagicNumber(): number[] {
return ParserSurfaceFlinger.MAGIC_NUMBER;
}
override decodeProto(buffer: Uint8Array): any[] {
return (<any>LayersTraceFileProto.decode(buffer)).entry;
}
override getTimestamp(entryProto: any): number {
return Number(entryProto.elapsedRealtimeNanos);
}
override processTraceEntryProto(entryProto: any): any {
return LayerTraceEntry.fromProto(entryProto.layers.layers, entryProto.displays, entryProto.elapsedRealtimeNanos, entryProto.hwcBlob);
}
private static readonly MAGIC_NUMBER = [0x09, 0x4c, 0x59, 0x52, 0x54, 0x52, 0x41, 0x43, 0x45]; // .LYRTRACE
}
export { ParserSurfaceFlinger };

View File

@@ -0,0 +1,47 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Parser} from "./parser"
import {ParserFactory} from './parser_factory';
import {TestUtils} from "test/test_utils";
describe("ParserTransactions", () => {
let parser: Parser;
beforeAll(() => {
const buffer = TestUtils.loadFixture("trace_Transactions.pb");
const parsers = new ParserFactory().createParsers([buffer]);
expect(parsers.length).toEqual(1);
parser = parsers[0];
});
it("has expected trace type", () => {
expect(parser.getTraceTypeId()).toEqual(TraceTypeId.TRANSACTIONS);
});
it("provides timestamps", () => {
const timestamps = parser.getTimestamps();
expect(timestamps.length)
.toEqual(4997);
expect(timestamps.slice(0, 3))
.toEqual([14862317023, 14873423549, 14884850511]);
});
it("retrieves trace entry", () => {
expect(Number(parser.getTraceEntry(14862317023)!.elapsedRealtimeNanos))
.toEqual(14862317023);
});
});

View File

@@ -0,0 +1,48 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Parser} from './parser'
import {TransactionsTraceFileProto} from './proto_types';
class ParserTransactions extends Parser {
constructor(buffer: Uint8Array) {
super(buffer);
}
override getTraceTypeId(): TraceTypeId {
return TraceTypeId.TRANSACTIONS;
}
override getMagicNumber(): number[] {
return ParserTransactions.MAGIC_NUMBER;
}
override decodeProto(buffer: Uint8Array): any[] {
return (<any>TransactionsTraceFileProto.decode(buffer)).entry;
}
override getTimestamp(entryProto: any): number {
return Number(entryProto.elapsedRealtimeNanos);
}
override processTraceEntryProto(entryProto: any): any {
return entryProto;
}
private static readonly MAGIC_NUMBER = [0x09, 0x54, 0x4e, 0x58, 0x54, 0x52, 0x41, 0x43, 0x45]; // .TNXTRACE
}
export {ParserTransactions};

View File

@@ -0,0 +1,46 @@
/*
* 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 {WindowManagerState} from 'common/trace/flickerlib/windows/WindowManagerState';
import {TraceTypeId} from "common/trace/type_id";
import {ParserFactory} from "./parser_factory";
import {Parser} from "./parser";
import {TestUtils} from "test/test_utils";
describe("ParserWindowManager", () => {
let parser: Parser;
beforeAll(() => {
const buffer = TestUtils.loadFixture("trace_WindowManager.pb");
const parsers = new ParserFactory().createParsers([buffer]);
expect(parsers.length).toEqual(1);
parser = parsers[0];
});
it("has expected trace type", () => {
expect(parser.getTraceTypeId()).toEqual(TraceTypeId.WINDOW_MANAGER);
});
it("provides timestamps", () => {
expect(parser.getTimestamps())
.toEqual([850254319343, 850763506110, 850782750048]);
});
it("retrieves trace entry", () => {
const entry = parser.getTraceEntry(850254319343)!;
expect(entry).toBeInstanceOf(WindowManagerState);
expect(Number(entry.timestampMs)).toEqual(850254319343);
});
});

View File

@@ -0,0 +1,49 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Parser} from './parser'
import {WindowManagerTraceFileProto} from './proto_types';
import {WindowManagerState} from 'common/trace/flickerlib/windows/WindowManagerState';
class ParserWindowManager extends Parser {
constructor(buffer: Uint8Array) {
super(buffer);
}
override getTraceTypeId(): TraceTypeId {
return TraceTypeId.WINDOW_MANAGER;
}
override getMagicNumber(): number[] {
return ParserWindowManager.MAGIC_NUMBER;
}
override decodeProto(buffer: Uint8Array): any {
return (<any>WindowManagerTraceFileProto.decode(buffer)).entry;
}
override getTimestamp(entryProto: any): number {
return Number(entryProto.elapsedRealtimeNanos);
}
override processTraceEntryProto(entryProto: any): WindowManagerState {
return WindowManagerState.fromProto(entryProto.windowManagerService, entryProto.elapsedRealtimeNanos, entryProto.where);
}
private static readonly MAGIC_NUMBER = [0x09, 0x57, 0x49, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x45]; // .WINTRACE
}
export {ParserWindowManager};

View File

@@ -0,0 +1,28 @@
import * as protobuf from "protobufjs";
import accessibilityJson from "frameworks/base/core/proto/android/server/accessibilitytrace.proto";
import inputMethodClientsJson from "frameworks/base/core/proto/android/view/inputmethod/inputmethodeditortrace.proto";
import layersJson from "frameworks/native/services/surfaceflinger/layerproto/layerstrace.proto";
import protoLogJson from "frameworks/base/core/proto/android/internal/protolog.proto";
import transactionsJson from "frameworks/native/services/surfaceflinger/layerproto/transactions.proto";
import windowManagerJson from "frameworks/base/core/proto/android/server/windowmanagertrace.proto";
const AccessibilityTraceFileProto = protobuf.Root.fromJSON(accessibilityJson).lookupType("com.android.server.accessibility.AccessibilityTraceFileProto");
const InputMethodClientsTraceFileProto = protobuf.Root.fromJSON(inputMethodClientsJson).lookupType("android.view.inputmethod.InputMethodClientsTraceFileProto");
const InputMethodManagerServiceTraceFileProto = protobuf.Root.fromJSON(inputMethodClientsJson).lookupType("android.view.inputmethod.InputMethodManagerServiceTraceFileProto");
const InputMethodServiceTraceFileProto = protobuf.Root.fromJSON(inputMethodClientsJson).lookupType("android.view.inputmethod.InputMethodServiceTraceFileProto");
const LayersTraceFileProto = protobuf.Root.fromJSON(layersJson).lookupType("android.surfaceflinger.LayersTraceFileProto");
const ProtoLogFileProto = protobuf.Root.fromJSON(protoLogJson).lookupType("com.android.internal.protolog.ProtoLogFileProto");
const TransactionsTraceFileProto = protobuf.Root.fromJSON(transactionsJson).lookupType("android.surfaceflinger.proto.TransactionTraceFile");
const WindowManagerTraceFileProto = protobuf.Root.fromJSON(windowManagerJson).lookupType("com.android.server.wm.WindowManagerTraceFileProto");
export {
AccessibilityTraceFileProto,
InputMethodClientsTraceFileProto,
InputMethodManagerServiceTraceFileProto,
InputMethodServiceTraceFileProto,
LayersTraceFileProto,
ProtoLogFileProto,
TransactionsTraceFileProto,
WindowManagerTraceFileProto
};

View File

@@ -0,0 +1,53 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes recent versions of Safari, Chrome (including
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,27 @@
/*
* 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 fs from 'fs';
import path from 'path';
class TestUtils {
static loadFixture(filename: string): Uint8Array {
const fullPath = path.resolve(__dirname, path.join("../src/test/fixtures", filename));
const data = fs.readFileSync(fullPath);
return new Uint8Array(data);
}
};
export {TestUtils};

View File

@@ -0,0 +1,24 @@
/*
* 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 { TraceTypeId } from "common/trace/type_id"
interface Viewer {
//TODO: add TraceEntry data type
notifyCurrentTraceEntries(entries: Map<TraceTypeId, any>): void;
getView(): HTMLElement;
}
export { Viewer };

View File

@@ -0,0 +1,42 @@
/*
* 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 { TraceTypeId } from "common/trace/type_id";
import { Viewer } from './viewer';
import { ViewerWindowManager } from './viewer_window_manager/viewer_window_manager';
class ViewerFactory {
static readonly VIEWERS = [
ViewerWindowManager,
]
public createViewers(activeTraceTypes: Set<TraceTypeId>): Viewer[] {
const viewers: Viewer[] = [];
for (const Viewer of ViewerFactory.VIEWERS) {
const areViewerDepsSatisfied = Viewer.DEPENDENCIES.every((traceType: TraceTypeId) =>
activeTraceTypes.has(traceType)
);
if (areViewerDepsSatisfied) {
viewers.push(new Viewer());
}
}
return viewers;
}
}
export { ViewerFactory };

View File

@@ -0,0 +1,53 @@
/*
* 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 {ViewerWindowManagerComponent} from './viewer_window_manager.component';
describe("ViewerWindowManagerComponent", () => {
let fixture: ComponentFixture<ViewerWindowManagerComponent>;
let component: ViewerWindowManagerComponent;
let htmlElement: HTMLElement;
beforeAll(async () => {
await TestBed.configureTestingModule({
declarations: [ViewerWindowManagerComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ViewerWindowManagerComponent);
component = fixture.componentInstance;
htmlElement = fixture.nativeElement;
});
it("can be created", () => {
expect(component).toBeTruthy();
});
it("renders the title", () => {
const divTitle = htmlElement.querySelector(".viewer-window-manager div.title");
expect(divTitle?.innerHTML).toContain("Window Manager");
});
it("renders the input value", async () => {
component.inputValue = new Date("2012-12-12");
fixture.detectChanges();
const divInputValue = htmlElement.querySelector(".viewer-window-manager div.input-value");
expect(divInputValue?.innerHTML).toContain("Wed Dec 12 2012 00:00:00 GMT+0000 (Coordinated Universal Time)");
});
});

View File

@@ -0,0 +1,45 @@
/*
* 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,
EventEmitter,
Input,
Output
} from '@angular/core';
@Component({
template: `
<div class="viewer-window-manager">
<div class="title">Window Manager</div>
<div class="input-value">Input value: {{inputValue}}</div>
<div class="button"><button mat-icon-button (click)="generateOutputEvent($event)">Output event!</button></div>
</div>
`
})
export class ViewerWindowManagerComponent {
@Input()
inputValue?: Date;
@Output()
outputEvent = new EventEmitter<DummyEvent>(); // or EventEmitter<void>()
public generateOutputEvent(event: MouseEvent) {
this.outputEvent.emit(new DummyEvent())
}
}
export class DummyEvent {
}

View File

@@ -0,0 +1,40 @@
/*
* 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 {TraceTypeId} from "common/trace/type_id";
import {Viewer} from 'viewers/viewer';
class ViewerWindowManager implements Viewer {
public static readonly DEPENDENCIES: TraceTypeId[] = [TraceTypeId.WINDOW_MANAGER];
private view: HTMLElement;
constructor() {
this.view = document.createElement("viewer-window-manager");
//this.view.setAttribute("input-value", new Date() as unknown as string)
(this.view as any).inputValue = new Date();
this.view.addEventListener("outputEvent", () => console.log("Viewer component generated event!"));
}
public notifyCurrentTraceEntries(entries: Map<TraceTypeId, any>): void {
}
public getView(): HTMLElement {
return this.view;
}
}
export {ViewerWindowManager};

View File

@@ -0,0 +1,36 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./src",
"outDir": "./dist/out-tsc",
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"target": "es2020",
"module": "es2020",
"lib": [
"es2020",
"dom"
],
"resolveJsonModule": true,
},
"include": ["src"],
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View File

@@ -0,0 +1,71 @@
/*
* 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.
*/
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
polyfills: "./src/polyfills.ts",
app: "./src/main.ts"
},
resolve: {
extensions: [".ts", ".js"],
modules: [
"node_modules",
"src",
"kotlin_build",
path.resolve(__dirname, '../../..'),
]
},
resolveLoader: {
modules: ['node_modules', path.resolve(__dirname, 'loaders')],
},
module: {
rules:[
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
},
{
test: /\.html$/,
use: ["html-loader"]
},
{
test: /\.proto$/,
loader: 'proto-loader',
options: {
paths: [
path.resolve(__dirname, '../../..'),
path.resolve(__dirname, '../../../external/protobuf/src'),
]
}
},
{
test: /\.ts$/,
use: ["ts-loader", "angular2-template-loader"]
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
}

View File

@@ -0,0 +1,24 @@
/*
* 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.
*/
const {merge} = require('webpack-merge');
const configCommon = require('./webpack.config.common');
const configDev = {
mode: 'development',
devtool: "source-map",
};
module.exports = merge(configCommon, configDev);

View File

@@ -0,0 +1,22 @@
/*
* 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.
*/
const environment = (process.env.NODE_ENV || 'development').trim();
if (environment === 'development') {
module.exports = require('./webpack.config.dev');
} else {
module.exports = require('./webpack.config.prod');
}

View File

@@ -0,0 +1,54 @@
/*
* 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.
*/
const {merge} = require('webpack-merge');
const configCommon = require('./webpack.config.common');
const path = require('path');
const configProd = {
mode: 'production',
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: 'js/[name].[hash].js',
chunkFilename: 'js/[name].[id].[hash].chunk.js',
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
const packageName = module.context
.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
return `npm.${packageName.replace('@', '')}`;
},
},
styles: {
test: /\.css$/,
name: 'styles',
chunks: 'all',
enforce: true,
},
},
},
}
};
module.exports = merge(configCommon, configProd);

View File

@@ -0,0 +1,40 @@
/*
* 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.
*/
const path = require('path');
const glob = require('glob');
let config = require('./webpack.config.common');
const allTestFiles = [
...glob.sync('./src/**/*.spec.js'),
...glob.sync('./src/**/*.spec.ts')
]
const unitTestFiles = allTestFiles.filter(file => !file.match(".*component\.spec\.(js|ts)$"))
config["entry"] = {
tests: unitTestFiles,
};
config["output"] = {
path: path.resolve(__dirname, './dist'),
filename: 'bundle.spec.js',
};
config["target"] = "node";
config["node"] = {
__dirname: false
}
module.exports = config;