Refactor WM transformations to use flickerlib object representation Test: Run winscope and make sure there are no errors and everything seems to still work... Change-Id: I1aef5c469d4323af502580e03a310d76da739d4d
diff --git a/tools/winscope/package.json b/tools/winscope/package.json index a917e3e..563add3 100644 --- a/tools/winscope/package.json +++ b/tools/winscope/package.json
@@ -27,6 +27,7 @@ "@babel/preset-env": "^7.10.4", "@babel/register": "^7.10.5", "@jetbrains/kotlin-webpack-plugin": "^3.0.2", + "@types/lodash": "^4.14.158", "babel-loader": "^8.1.0", "compression-webpack-plugin": "^4.0.0", "cross-env": "^7.0.2", @@ -47,6 +48,8 @@ "protobufjs": "^6.10.0", "source-map-loader": "^1.0.1", "style-loader": "^1.2.1", + "ts-loader": "^8.0.1", + "typescript": "^3.9.7", "uglifyjs-webpack-plugin": "^2.2.0", "vue-loader": "^15.9.3", "vue-style-loader": "^4.1.2",
diff --git a/tools/winscope/src/DefaultTreeElement.vue b/tools/winscope/src/DefaultTreeElement.vue index 5614607..1df8d17 100644 --- a/tools/winscope/src/DefaultTreeElement.vue +++ b/tools/winscope/src/DefaultTreeElement.vue
@@ -35,6 +35,13 @@ :title="c.long" :class="chipClassForChip(c)" >{{c.short}} <!-- No line break on purpose --> + <md-tooltip + md-delay="300" + md-direction="top" + style="margin-bottom: -10px" + > + {{c.long}} + </md-tooltip> </div> </span> </template>
diff --git a/tools/winscope/src/Rects.vue b/tools/winscope/src/Rects.vue index 06d23a4..4b732d7 100644 --- a/tools/winscope/src/Rects.vue +++ b/tools/winscope/src/Rects.vue
@@ -15,7 +15,7 @@ <template> <div class="bounds" :style="boundsStyle"> <div - class="rect" v-for="r in rects" + class="rect" v-for="r in filteredRects" :style="rectToStyle(r)" @click="onClick(r)" v-bind:key="`${r.left}-${r.right}-${r.top}-${r.bottom}-${r.ref.name}`" @@ -55,6 +55,9 @@ return this.rectToStyle({top: 0, left: 0, right: this.boundsC.width, bottom: this.boundsC.height}); }, + filteredRects() { + return this.rects.filter((rect) => rect.ref.visible); + }, }, methods: { s(sourceCoordinate) { // translate source into target coordinates
diff --git a/tools/winscope/src/TraceView.vue b/tools/winscope/src/TraceView.vue index 3dbc941..52016b7 100644 --- a/tools/winscope/src/TraceView.vue +++ b/tools/winscope/src/TraceView.vue
@@ -209,7 +209,7 @@ itemSelected(item) { this.hierarchySelected = item; this.selectedTree = this.getTransformedProperties(item); - this.highlight = item.highlight; + this.highlight = item.rect; this.lastSelectedStableId = item.stableId; this.$emit('focus'); },
diff --git a/tools/winscope/src/decode.js b/tools/winscope/src/decode.js index eb72e08..42c3830 100644 --- a/tools/winscope/src/decode.js +++ b/tools/winscope/src/decode.js
@@ -26,7 +26,6 @@ import jsonProtoDefsLauncher from 'packages/apps/Launcher3/protos/launcher_trace_file.proto'; import protobuf from 'protobufjs'; import {transformLayers, transformLayersTrace} from './transform_sf.js'; -import {transform_window_service, transform_window_trace} from './transform_wm.js'; import {transform_transaction_trace} from './transform_transaction.js'; import {transform_wl_outputstate, transform_wayland_trace} from './transform_wl.js'; import {transformProtolog} from './transform_protolog.js'; @@ -211,7 +210,7 @@ decoderParams: { type: FILE_TYPES.WINDOW_MANAGER_TRACE, protoType: WmTraceMessage, - transform: transform_window_trace, + transform: WindowManagerTrace.fromProto, timeline: true, }, }, @@ -255,7 +254,7 @@ type: FILE_TYPES.WINDOW_MANAGER_DUMP, mime: 'application/octet-stream', protoType: WmDumpMessage, - transform: transform_window_service, + transform: WindowManagerDump.fromProto, timeline: false, }, }, @@ -380,7 +379,7 @@ const transformed = decodeAndTransformProto(buffer, params, store.displayDefaults); let data; if (params.timeline) { - data = transformed.children; + data = transformed.entries ?? transformed.children; } else { data = [transformed]; }
diff --git a/tools/winscope/src/dumps/WindowManager.ts b/tools/winscope/src/dumps/WindowManager.ts index 1203e3e..4a01ef7 100644 --- a/tools/winscope/src/dumps/WindowManager.ts +++ b/tools/winscope/src/dumps/WindowManager.ts
@@ -17,6 +17,8 @@ import { FILE_TYPES, DUMP_TYPES } from "@/decode.js"; import DumpBase from "./DumpBase"; +import { WindowManagerTraceEntry } from '@/flickerlib'; + export default class WindowManager extends DumpBase { wmDumpFile: any; @@ -29,4 +31,8 @@ get type() { return DUMP_TYPES.WINDOW_MANAGER; } + + static fromProto(proto): WindowManagerTraceEntry { + return WindowManagerTraceEntry.fromProto(proto); + } } \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/README.md b/tools/winscope/src/flickerlib/README.md new file mode 100644 index 0000000..79cbbe6 --- /dev/null +++ b/tools/winscope/src/flickerlib/README.md
@@ -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. \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/WindowManagerTrace.ts b/tools/winscope/src/flickerlib/WindowManagerTrace.ts new file mode 100644 index 0000000..67f192b --- /dev/null +++ b/tools/winscope/src/flickerlib/WindowManagerTrace.ts
@@ -0,0 +1,36 @@ +/* + * 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 { + WindowManagerTrace, +} from "./common" + +import WindowManagerTraceEntry from "./WindowManagerTraceEntry" + +WindowManagerTrace.fromProto = function (proto) { + const entries = [] + for (const entryProto of proto.entry) { + const transformedEntry = WindowManagerTraceEntry.fromProto( + entryProto.windowManagerService, entryProto.elapsedRealtimeNanos) + entries.push(transformedEntry) + } + + const source = null; + const sourceChecksum = null; + return new WindowManagerTrace(entries, source, sourceChecksum) +} + +export default WindowManagerTrace; \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/WindowManagerTraceEntry.ts b/tools/winscope/src/flickerlib/WindowManagerTraceEntry.ts new file mode 100644 index 0000000..3b76733 --- /dev/null +++ b/tools/winscope/src/flickerlib/WindowManagerTraceEntry.ts
@@ -0,0 +1,113 @@ +/* + * 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 { nanosToString, TimeUnits } from "../utils/utils.js" + +import { WindowManagerTraceEntry } from "./common" + +import { applyMixins } from "./mixin" + +import ITreeViewElement from './treeview/IClickableTreeViewElement' +import IClickableTreeViewElement from './treeview/IClickableTreeViewElement' +import Chip from './treeview/Chip' +import WindowContainer from "./windows/WindowContainer" + +class WindowManagerTraceEntryMixin implements IClickableTreeViewElement { + common: any + kind: string + name: string + shortName: string + stableId: string + chips: Array<Chip> + children: Array<ITreeViewElement> + obj: any + rawTreeViewObject + + timestamp: number + rootWindow + + mixinConstructor(obj) { + const name = this.timestamp ? nanosToString(this.timestamp, TimeUnits.MILLI_SECONDS) : "" + this.kind = "entry" + this.name = name + this.shortName = name + this.stableId = "entry" + this.children = this.rootWindow.children + this.chips = [] + this.obj = obj + } + + static fromProto(proto, timestamp) { + const rootWindow = + WindowContainer.fromProto(proto.rootWindowContainer.windowContainer) + const windowManagerTraceEntry = + new WindowManagerTraceEntry(rootWindow, timestamp) + + windowManagerTraceEntry.kind = 'service' + + windowManagerTraceEntry.focusedApp = proto.focusedApp + windowManagerTraceEntry.focusedDisplayId = proto.focusedDisplayId + windowManagerTraceEntry.lastOrientation = proto.lastOrientation + windowManagerTraceEntry.policy = proto.policy + windowManagerTraceEntry.rotation = proto.rotation + windowManagerTraceEntry.displayFrozen = proto.displayFrozen + windowManagerTraceEntry.inputMethodWindow = proto.inputMethodWindow + + // Remove anything that is part of the children elements + // allows for faster loading of properties and less information cluttering + // this applied to anywhere the proto is passed to be saved as .obj + const obj = Object.assign({}, proto) + obj.rootWindowContainer = {}; + Object.assign(obj.rootWindowContainer, + proto.rootWindowContainer) + obj.rootWindowContainer.windowContainer = {}; + Object.assign(obj.rootWindowContainer.windowContainer, + proto.rootWindowContainer.windowContainer) + delete obj.rootWindowContainer.windowContainer.children + windowManagerTraceEntry.mixinConstructor(obj) + + return windowManagerTraceEntry + } + + attachObject(obj) { + this.obj = obj + } + + asRawTreeViewObject() { + // IMPORTANT: We want to always return the same tree view object and not + // generate a new one every time this function is called. + if (!this.rawTreeViewObject) { + const children = this.children.map(child => child.asRawTreeViewObject()) + + this.rawTreeViewObject = { + kind: this.kind, + name: this.name, + shortName: this.shortName, + stableId: this.stableId, + chips: this.chips, + obj: this.obj, + children, + ref: this, + } + } + + return this.rawTreeViewObject; + } +} + +applyMixins(WindowManagerTraceEntry, [WindowManagerTraceEntryMixin]) + +export default WindowManagerTraceEntry; \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/common.js b/tools/winscope/src/flickerlib/common.js new file mode 100644 index 0000000..44f70d1 --- /dev/null +++ b/tools/winscope/src/flickerlib/common.js
@@ -0,0 +1,58 @@ +/* + * 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 + +const WindowManagerTrace = require('flicker').com.android.server.wm.flicker + .common.traces.windowmanager.WindowManagerTrace; +const WindowManagerTraceEntry = require('flicker').com.android.server.wm. + flicker.common.traces.windowmanager.WindowManagerTraceEntry; + +const WindowContainer = require('flicker').com.android.server.wm.flicker.common. + traces.windowmanager.windows.WindowContainer; +const WindowState = require('flicker').com.android.server.wm.flicker.common. + traces.windowmanager.windows.WindowState; +const DisplayContent = require('flicker').com.android.server.wm.flicker.common. + traces.windowmanager.windows.DisplayContent; +const ActivityRecord = require('flicker').com.android.server.wm.flicker.common. + traces.windowmanager.windows.ActivityRecord; +const WindowToken = require('flicker').com.android.server.wm.flicker.common. + traces.windowmanager.windows.WindowToken; +const DisplayArea = require('flicker').com.android.server.wm.flicker.common. + traces.windowmanager.windows.DisplayArea; +const RootDisplayArea = require('flicker').com.android.server.wm.flicker.common. + traces.windowmanager.windows.RootDisplayArea; +const Task = require('flicker').com.android.server.wm.flicker.common.traces. + windowmanager.windows.Task; + +const Rect = require('flicker').com.android.server.wm.flicker.common.Rect; +const Bounds = require('flicker').com.android.server.wm.flicker.common.Bounds; + +export { + WindowManagerTrace, + WindowManagerTraceEntry, + WindowContainer, + WindowState, + DisplayContent, + ActivityRecord, + WindowToken, + DisplayArea, + RootDisplayArea, + Task, + Rect, + Bounds, +};
diff --git a/tools/winscope/src/flickerlib/index.js b/tools/winscope/src/flickerlib/index.js new file mode 100644 index 0000000..9c731b1 --- /dev/null +++ b/tools/winscope/src/flickerlib/index.js
@@ -0,0 +1,25 @@ +/* + * 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 WindowManagerTraceEntry from './WindowManagerTraceEntry'; +import WindowManagerTrace from './WindowManagerTrace'; + +/** + * Entry point into the flickerlib for Winscope. + * Expose everything we want Winscope to have access to here. + */ +export {WindowManagerTraceEntry, WindowManagerTrace}; +
diff --git a/tools/winscope/src/flickerlib/mixin.ts b/tools/winscope/src/flickerlib/mixin.ts new file mode 100644 index 0000000..5e6c836 --- /dev/null +++ b/tools/winscope/src/flickerlib/mixin.ts
@@ -0,0 +1,23 @@ +/** + * Injects all the properties (getters, setters, functions...) of a list of + * classes (baseCtors) into a class (derivedCtor). + * @param derivedCtor The constructor of the class we want to inject the + * properties into. + * @param baseCtors A list of consturctor of the classes we want to mixin the + * properties of into the derivedCtor. + */ +export function applyMixins(derivedCtor: any, baseCtors: any[]) { + baseCtors.forEach(baseCtor => { + Object.getOwnPropertyNames(baseCtor).forEach(name => { + if (['length', 'name', 'prototype'].includes(name)) { + return; + } + + Object.defineProperty(derivedCtor, name, Object.getOwnPropertyDescriptor(baseCtor, name)) + }) + + Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { + Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name)) + }) + }); +} \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/treeview/Chip.ts b/tools/winscope/src/flickerlib/treeview/Chip.ts new file mode 100644 index 0000000..30f1c83 --- /dev/null +++ b/tools/winscope/src/flickerlib/treeview/Chip.ts
@@ -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 + } +} \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/treeview/ChipType.ts b/tools/winscope/src/flickerlib/treeview/ChipType.ts new file mode 100644 index 0000000..70c25d7 --- /dev/null +++ b/tools/winscope/src/flickerlib/treeview/ChipType.ts
@@ -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 \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/treeview/Chips.ts b/tools/winscope/src/flickerlib/treeview/Chips.ts new file mode 100644 index 0000000..8e1ebd1 --- /dev/null +++ b/tools/winscope/src/flickerlib/treeview/Chips.ts
@@ -0,0 +1,20 @@ +/* + * 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) \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/treeview/IClickableTreeViewElement.ts b/tools/winscope/src/flickerlib/treeview/IClickableTreeViewElement.ts new file mode 100644 index 0000000..7b869cd --- /dev/null +++ b/tools/winscope/src/flickerlib/treeview/IClickableTreeViewElement.ts
@@ -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. + */ + +import ITreeViewElement from "./ITreeViewElement" + +export default interface IClickableTreeViewElement extends ITreeViewElement { + obj: any +} \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/treeview/ITreeViewElement.ts b/tools/winscope/src/flickerlib/treeview/ITreeViewElement.ts new file mode 100644 index 0000000..8e5ab74 --- /dev/null +++ b/tools/winscope/src/flickerlib/treeview/ITreeViewElement.ts
@@ -0,0 +1,34 @@ +/* + * 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 { TreeViewObject } from "./types" + +export default interface ITreeViewElement { + kind: String + name: String + shortName: String + stableId: Number | String + chips: Chip[] + children: ITreeViewElement[] + + // This is used for compatibility with the "legacy" Winscope infrastructure + // where a class object would cause things to not work properly so this should + // return a raw javascript object with the relevant information. + // IMPORTANT: The implementation of this function should always return the + // same object every time it is called and not generate a new object. + asRawTreeViewObject(): TreeViewObject +} \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/treeview/types.ts b/tools/winscope/src/flickerlib/treeview/types.ts new file mode 100644 index 0000000..1c5c942 --- /dev/null +++ b/tools/winscope/src/flickerlib/treeview/types.ts
@@ -0,0 +1,12 @@ +import Chip from './Chip' + +export type TreeViewObject = { + kind: String + name: String + shortName: String + stableId: String | Number + chips: Chip[] + obj: any + children: TreeViewObject[] + ref: any +} \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/windows/ActivityRecord.ts b/tools/winscope/src/flickerlib/windows/ActivityRecord.ts new file mode 100644 index 0000000..b3146f6 --- /dev/null +++ b/tools/winscope/src/flickerlib/windows/ActivityRecord.ts
@@ -0,0 +1,46 @@ +/* + * 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 { + ActivityRecord, +} from "../common" + +import { applyMixins } from '../mixin' + +import WindowToken from "./WindowToken" + +export class ActivityRecordMixin { + get kind() { + return "DisplayArea" + } + + static fromProto(proto) { + const windowToken = WindowToken.fromProto(proto.windowToken) + + const activityRecord = new ActivityRecord(windowToken) + + const obj = Object.assign({}, proto) + delete proto.windowToken + Object.assign(obj, windowToken.obj) + activityRecord.attachObject(obj) + + return activityRecord + } +} + +applyMixins(ActivityRecord, [ActivityRecordMixin]) + +export default ActivityRecord \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/windows/DisplayArea.ts b/tools/winscope/src/flickerlib/windows/DisplayArea.ts new file mode 100644 index 0000000..b497d02 --- /dev/null +++ b/tools/winscope/src/flickerlib/windows/DisplayArea.ts
@@ -0,0 +1,46 @@ +/* + * 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 { + DisplayArea, +} from "../common" + +import { applyMixins } from '../mixin' + +import WindowContainer from "./WindowContainer" + +export class DisplayAreaMixin { + get kind() { + return "DisplayArea" + } + + static fromProto(proto) { + const windowContainer = WindowContainer.fromProto(proto.windowContainer) + + const displayArea = new DisplayArea(windowContainer) + + const obj = Object.assign({}, proto) + delete obj.windowContainer + Object.assign(obj, windowContainer.obj) + displayArea.attachObject(obj) + + return displayArea + } +} + +applyMixins(DisplayArea, [DisplayAreaMixin]) + +export default DisplayArea \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/windows/DisplayContent.ts b/tools/winscope/src/flickerlib/windows/DisplayContent.ts new file mode 100644 index 0000000..06cd78d --- /dev/null +++ b/tools/winscope/src/flickerlib/windows/DisplayContent.ts
@@ -0,0 +1,62 @@ +/* + * 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 { + DisplayContent, + Bounds +} from "../common" + +import { applyMixins } from '../mixin' + +import WindowContainer from "./WindowContainer" +import DisplayArea from "./DisplayArea" + +export class DisplayContentMixin { + get kind() { + return "DisplayContent" + } + + static fromProto(proto) { + let rootDisplayArea; + if (proto.rootDisplayArea.windowContainer == null) { + // For backward compatibility + const windowContainer = WindowContainer.fromProto(proto.windowContainer) + rootDisplayArea = new DisplayArea(windowContainer) + } else { + // New protos should always be using this + rootDisplayArea = DisplayArea.fromProto(proto.rootDisplayArea) + } + + const bounds = new Bounds( + proto.displayInfo.logicalWidth || 0, + proto.displayInfo.logicalHeight || 0, + ) + + const displayContent = new DisplayContent(rootDisplayArea, bounds) + + const obj = Object.assign({}, proto) + delete obj.windowContainer + delete obj.rootDisplayArea + Object.assign(obj, rootDisplayArea.obj) + displayContent.attachObject(obj) + + return displayContent + } +} + +applyMixins(DisplayContent, [DisplayContentMixin]) + +export default DisplayContent \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/windows/RootDisplayArea.ts b/tools/winscope/src/flickerlib/windows/RootDisplayArea.ts new file mode 100644 index 0000000..c7acb13 --- /dev/null +++ b/tools/winscope/src/flickerlib/windows/RootDisplayArea.ts
@@ -0,0 +1,22 @@ +/* + * 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 { RootDisplayArea } from "../common" +import DisplayArea from "./DisplayArea" + +RootDisplayArea.fromProto = DisplayArea.fromProto + +export default DisplayArea \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/windows/Task.ts b/tools/winscope/src/flickerlib/windows/Task.ts new file mode 100644 index 0000000..d3b6eba --- /dev/null +++ b/tools/winscope/src/flickerlib/windows/Task.ts
@@ -0,0 +1,44 @@ +/* + * 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 { Task } from "../common" + +import { applyMixins } from '../mixin' + +import WindowContainer from "./WindowContainer" + +export class TaskMixin { + get kind() { + return "Task" + } + + static fromProto(proto) { + const windowContainer = WindowContainer.fromProto(proto.windowContainer) + + const task = new Task(windowContainer) + + const obj = Object.assign({}, proto) + delete obj.windowContainer + Object.assign(obj, windowContainer.obj) + task.attachObject(obj) + + return task + } +} + +applyMixins(Task, [TaskMixin]) + +export default Task \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/windows/WindowContainer.ts b/tools/winscope/src/flickerlib/windows/WindowContainer.ts new file mode 100644 index 0000000..d591739 --- /dev/null +++ b/tools/winscope/src/flickerlib/windows/WindowContainer.ts
@@ -0,0 +1,204 @@ +/* + * 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 { WindowContainer } from "../common" + +import { applyMixins } from '../mixin' + +import IClickableTreeViewElement from '../treeview/IClickableTreeViewElement' +import { TreeViewObject } from '../treeview/types' + +import DisplayArea from "./DisplayArea" +import DisplayContent from "./DisplayContent" +import Task from "./Task" +import ActivityRecord from "./ActivityRecord" +import WindowToken from "./WindowToken" +import WindowState from "./WindowState" + +import { CompatibleFeatures } from '@/utils/compatibility.js' + +export class WindowContainerMixin implements IClickableTreeViewElement { + title: string + windowHashCode: number + childrenWindows + visible: boolean + rawTreeViewObject + + _obj: { name: string } + _children + + get kind() { + return "WindowContainer" + } + + get name() { + if (this.obj && this.obj.name) { + return this.obj.name + } + + if (["WindowContainer", "Task"].includes(this.title)) { + return null + } + + return `${removeRedundancyInName(this.title)}@${this.windowHashCode}` + } + + get shortName() { + return this.name ? shortenName(this.name) : null + } + + get stableId() { + return this.windowHashCode + } + + get children() { + return this._children ?? this.childrenWindows + } + + set children(children) { + this._children = children + } + + get chips() { + return [] + } + + set obj(obj) { + this._obj = obj + } + + get obj() { + return this._obj + } + + static fromProto(proto) { + const children = proto.children.map( + child => transformWindowContainerChildProto(child)) + + // A kind of hacky way to check if the proto is from a device which + // didn't have the changes required for the diff viewer to work properly + // We required that all elements have a stable identifier in order to do the + // diff properly. In theory properties diff would still work, but are + // currently disabled. If required in the future don't hesitate to make + // the required changes. + if (!proto.identifier) { + CompatibleFeatures.DiffVisualization = false; + } + + + const title = proto.identifier.title + const hashCode = proto.identifier.hashCode + const visible = proto.visible + const windowContainer = + new WindowContainer(children, title, hashCode, visible) + + const obj = Object.assign({}, proto) + // 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. + delete obj.children + windowContainer.attachObject(obj) + + return windowContainer + } + + attachObject(obj) { + this._obj = obj + } + + asRawTreeViewObject(): TreeViewObject { + // IMPORTANT: We want to always return the same tree view object and not + // generate a new one every time this function is called. + if (!this.rawTreeViewObject) { + const children = this.children.map(child => child.asRawTreeViewObject()) + + this.rawTreeViewObject = { + kind: this.kind, + name: this.name, + shortName: this.shortName, + stableId: this.stableId, + chips: this.chips, + obj: this.obj, + children, + ref: this, + }; + } + + return this.rawTreeViewObject; + } +} + +applyMixins(WindowContainer, [WindowContainerMixin]) + +function transformWindowContainerChildProto(proto) { + if (proto.displayArea != null) { + return DisplayArea.fromProto(proto.displayArea) + } + + if (proto.displayContent != null) { + return DisplayContent.fromProto(proto.displayContent) + } + + if (proto.task != null) { + return Task.fromProto(proto.task) + } + + if (proto.activity != null) { + return ActivityRecord.fromProto(proto.activity) + } + + if (proto.windowToken != null) { + return WindowToken.fromProto(proto.windowToken) + } + + if (proto.window != null) { + return WindowState.fromProto(proto.window) + } + + throw new Error("Unhandled WindowContainerChildProto case...") +} + +function removeRedundancyInName(name: string): string { + if (!name.includes('/')) { + return name + } + + const split = name.split('/') + const pkg = split[0] + var clazz = split.slice(1).join("/") + + if (clazz.startsWith("$pkg.")) { + clazz = clazz.slice(pkg.length + 1) + + return "$pkg/$clazz" + } + + return name +} + +function shortenName(name: string): 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}` +} + +export default WindowContainer \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/windows/WindowState.ts b/tools/winscope/src/flickerlib/windows/WindowState.ts new file mode 100644 index 0000000..281b3b0 --- /dev/null +++ b/tools/winscope/src/flickerlib/windows/WindowState.ts
@@ -0,0 +1,55 @@ +/* + * 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 { WindowState, Rect } from "../common" +import { VISIBLE_CHIP } from '../treeview/Chips' + +import { applyMixins } from '../mixin' + +import WindowContainer from "./WindowContainer" + +export class WindowStateMixin { + visible: boolean + + get kind() { + return "WindowState" + } + + static fromProto(proto) { + const windowContainer = WindowContainer.fromProto(proto.windowContainer) + + const frame = (proto.windowFrames ?? proto).frame ?? {} + const rect = new Rect(frame.left ?? 0, frame.top ?? 0, frame.right ?? 0, frame.bottom ?? 0) + + const windowState = + new WindowState(windowContainer, /* childWindows */[], rect) + + const obj = Object.assign({}, proto) + Object.assign(obj, windowContainer.obj) + delete obj.windowContainer + windowState.attachObject(obj) + + return windowState + } + + get chips() { + return this.visible ? [VISIBLE_CHIP] : [] + } +} + +applyMixins(WindowState, [WindowStateMixin]) + +export default WindowState \ No newline at end of file
diff --git a/tools/winscope/src/flickerlib/windows/WindowToken.ts b/tools/winscope/src/flickerlib/windows/WindowToken.ts new file mode 100644 index 0000000..07e17cb --- /dev/null +++ b/tools/winscope/src/flickerlib/windows/WindowToken.ts
@@ -0,0 +1,44 @@ +/* + * 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 { WindowToken } from "../common" + +import { applyMixins } from '../mixin' + +import WindowContainer from "./WindowContainer" + +export class WindowContainerMixin { + get kind() { + return "WindowToken" + } + + static fromProto(proto) { + const windowContainer = WindowContainer.fromProto(proto.windowContainer) + + const windowToken = new WindowToken(windowContainer) + + const obj = Object.assign({}, proto) + Object.assign(obj, windowContainer.obj) + delete obj.windowContainer + windowToken.attachObject(obj) + + return windowToken + } +} + +applyMixins(WindowToken, [WindowContainerMixin]) + +export default WindowToken \ No newline at end of file
diff --git a/tools/winscope/src/traces/WindowManager.ts b/tools/winscope/src/traces/WindowManager.ts index 13c5fc1..bcf5026 100644 --- a/tools/winscope/src/traces/WindowManager.ts +++ b/tools/winscope/src/traces/WindowManager.ts
@@ -17,6 +17,8 @@ import { FILE_TYPES, TRACE_TYPES } from '@/decode.js'; import TraceBase from './TraceBase'; +import { WindowManagerTrace } from '@/flickerlib'; + export default class WindowManager extends TraceBase { wmTraceFile: Object; @@ -30,4 +32,8 @@ get type() { return TRACE_TYPES.WINDOW_MANAGER; } + + static fromProto(proto): WindowManagerTrace { + return WindowManagerTrace.fromProto(proto); + } }
diff --git a/tools/winscope/src/transform_wm.js b/tools/winscope/src/transform_wm.js deleted file mode 100644 index d2bb9bd..0000000 --- a/tools/winscope/src/transform_wm.js +++ /dev/null
@@ -1,276 +0,0 @@ -/* - * Copyright 2017, 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, nanos_to_string, get_visible_chip } from './transform.js' -import { CompatibleFeatures } from './utils/compatibility.js' -import { getComponentClassName } from './utils/names'; - -function transform_window(entry) { - const chips = []; - const renderIdentifier = (id) => shortenComponentName(id.title) + "@" + id.hashCode; - const visible = entry.windowContainer.visible; - function transform_rect(rect, label) { - const r = rect || {}; - return { - left: r.left || 0, - right: r.right || 0, - top: r.top || 0, - bottom: r.bottom || 0, - label, - } - } - - // For backward compatibility - if (!entry.windowContainer?.identifier) { - CompatibleFeatures.DiffVisualization = false; - } - const identifier = entry.windowContainer?.identifier ?? entry.identifier; - const name = renderIdentifier(identifier); - const shortName = getComponentClassName(identifier.title); - let rect = transform_rect((entry.windowFrames || entry).frame, name); - - if (visible) { - chips.push(get_visible_chip()); - } else { - rect = undefined; - } - - return transform({ - obj: entry, - kind: 'window', - name, - shortName, - stableId: entry.windowContainer?.identifier?.hashCode, - children: [ - [entry.childWindows, transform_window], - [entry.windowContainer.children.reverse(), transform_window_container_child], - ], - rect, - highlight: rect, - chips: chips, - visible: visible, - }); -} - -function transform_activity_record(entry) { - return transform({ - obj: entry, - kind: 'activityRecord', - name: entry.name, - shortName: getComponentClassName(entry.name), - stableId: entry.windowToken.windowContainer?.identifier?.hashCode, - children: [ - [entry.windowToken.windows, transform_window], - [entry.windowToken.windowContainer.children.reverse(), transform_window_container_child], - ], - }); -} - -function transform_task(entry) { - return transform({ - obj: entry, - kind: 'task', - name: entry.id || 0, - stableId: entry.windowContainer?.identifier?.hashCode, - children: [ - [entry.tasks, transform_task], - [entry.activities, transform_activity_record], - [entry.windowContainer.children.reverse(), transform_window_container_child], - ], - }); -} - -function transform_stack(entry) { - return transform({ - obj: entry, - kind: 'stack', - name: entry.id || 0, - children: [ - [entry.tasks, transform_task], - ], - }); -} - -function transform_window_token(entry) { - return transform({ - obj: entry, - kind: 'winToken', - name: '', - stableId: entry.windowContainer?.identifier?.hashCode, - children: [ - [entry.windows, transform_window], - [entry.windowContainer.children.reverse(), transform_window_container_child], - ], - }); -} - -function transform_below(entry) { - return transform({ - obj: entry, - kind: 'belowAppWindow', - name: '', - children: [ - [entry.windows, transform_window], - ], - }); -} - -function transform_above(entry) { - return transform({ - obj: entry, - kind: 'aboveAppWindow', - name: '', - children: [ - [entry.windows, transform_window], - ], - }); -} - -function transform_ime(entry) { - return transform({ - obj: entry, - kind: 'imeWindow', - name: '', - children: [ - [entry.windows, transform_window], - ], - }); -} - -function transform_window_container_child(entry) { - if (entry.displayArea != null) { return transform_display_area(entry.displayArea) } - if (entry.displayContent != null) { return transform_display_content(entry.displayContent) } - if (entry.task != null) { return transform_task(entry.task) } - if (entry.activity != null) { return transform_activity_record(entry.activity) } - if (entry.windowToken != null) { return transform_window_token(entry.windowToken) } - if (entry.window != null) { return transform_window(entry.window) } - - // The WindowContainerChild may be unknown - return transform({ - obj: entry, - kind: 'WindowContainerChild', - name: '', - stableId: entry.windowContainer?.identifier?.hashCode, - children: [[entry.windowContainer.children.reverse(), transform_window_container_child],] - }); -} - - -function transform_display_area(entry) { - return transform({ - obj: entry, - kind: 'DisplayArea', - name: entry.name, - stableId: entry.windowContainer?.identifier?.hashCode, - children: [ - [entry.windowContainer.children.reverse(), transform_window_container_child], - ], - }); -} - -function transform_display_content(entry) { - var bounds = { - width: entry.displayInfo.logicalWidth || 0, - height: entry.displayInfo.logicalHeight || 0, - }; - var children = entry.windowContainer == null - ? entry.rootDisplayArea.windowContainer.children.reverse() - : entry.windowContainer.children.reverse(); - - return transform({ - obj: entry, - kind: 'display', - name: entry.id || 0, - stableId: entry.windowContainer?.identifier?.hashCode, - children: [ - [entry.aboveAppWindows, transform_above], - [entry.imeWindows, transform_ime], - [entry.stacks, transform_stack], - [entry.tasks, transform_task], - [entry.belowAppWindows, transform_below], - [children, transform_window_container_child], - ], - bounds, - }); -} - -function transform_policy(entry) { - return transform({ - obj: entry, - kind: 'policy', - name: 'policy', - stableId: 'policy', - children: [], - }); -} - -function transform_window_service(entry) { - return transform({ - obj: entry, - kind: 'service', - name: '', - children: [ - [entry.rootWindowContainer.displays, transform_display_content], - [entry.rootWindowContainer.windowContainer.children.reverse(), - transform_window_container_child], - [[entry.policy], transform_policy], - ], - timestamp: entry.elapsedRealtimeNanos, - }); -} - -function transform_entry(entry) { - return transform({ - obj: entry, - kind: 'entry', - name: nanos_to_string(entry.elapsedRealtimeNanos), - children: [ - [entry.windowManagerService.rootWindowContainer.displays, transform_display_content], - [entry.windowManagerService.rootWindowContainer.windowContainer.children.reverse(), - transform_window_container_child], - [[entry.windowManagerService.policy], transform_policy], - ], - timestamp: entry.elapsedRealtimeNanos, - stableId: 'entry', - }); -} - -function transform_window_trace(entries) { - return transform({ - obj: entries, - kind: 'entries', - name: 'entries', - children: [ - [entries.entry, transform_entry], - ], - }); -} - -function shortenComponentName(name) { - if (!name.includes('/')) { - return name - } - var split = name.split('/'); - var pkg = split[0]; - var clazz = split.slice(1).join('/'); - if (clazz.startsWith(pkg + '.')) { - clazz = clazz.slice(pkg.length + 1); - return [pkg, clazz].join('/'); - } - return name; -} - -export { transform_window_service, transform_window_trace };
diff --git a/tools/winscope/src/utils/compatibility.js b/tools/winscope/src/utils/compatibility.js index bc90da5..5685f37 100644 --- a/tools/winscope/src/utils/compatibility.js +++ b/tools/winscope/src/utils/compatibility.js
@@ -1,3 +1,19 @@ +/* + * 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. + */ + const CompatibleFeatures = { DiffVisualization: true, }
diff --git a/tools/winscope/src/utils/consts.js b/tools/winscope/src/utils/consts.js index 37f219f..86799d9 100644 --- a/tools/winscope/src/utils/consts.js +++ b/tools/winscope/src/utils/consts.js
@@ -1,3 +1,19 @@ +/* + * 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. + */ + /** * Should be kept in sync with ENUM is in Google3 under: * google3/wireless/android/tools/android_bug_tool/extension/common/actions
diff --git a/tools/winscope/src/utils/diff.js b/tools/winscope/src/utils/diff.js index 080bb44..7b07606 100644 --- a/tools/winscope/src/utils/diff.js +++ b/tools/winscope/src/utils/diff.js
@@ -1,236 +1,263 @@ +/* + * 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. + */ + +// TODO (b/162300507): Get rid of cloning import cloneDeep from 'lodash.clonedeep'; -const DiffType = Object.freeze({ - NONE: 'none', - ADDED: 'added', - DELETED: 'deleted', - ADDED_MOVE: 'addedMove', - DELETED_MOVE: 'deletedMove', - MODIFIED: 'modified', +export const DiffType = Object.freeze({ + NONE: 'none', + ADDED: 'added', + DELETED: 'deleted', + ADDED_MOVE: 'addedMove', + DELETED_MOVE: 'deletedMove', + MODIFIED: 'modified', }); -function defaultModifiedCheck(newNode, oldNode) { - if (!newNode && !oldNode) { - return false; - } +export function defaultModifiedCheck(newNode, oldNode) { + if (!newNode && !oldNode) { + return false; + } - if ((newNode && !oldNode) || (!newNode && oldNode)) { - return true; - } + if ((newNode && !oldNode) || (!newNode && oldNode)) { + return true; + } - return JSON.stringify(newNode.obj) !== JSON.stringify(oldNode.obj); + return JSON.stringify(newNode.obj) !== JSON.stringify(oldNode.obj); } function isPrimitive(test) { - return test !== Object(test); -}; - -class DiffGenerator { - constructor(tree) { - this.tree = tree; - } - - compareWith(tree) { - this.diffWithTree = tree; - return this; - } - - withUniqueNodeId(getNodeId) { - this.getNodeId = node => { - const id = getNodeId(node); - if (id === null || id === undefined) { - console.error("Null node ID for node", node); - throw new Error("Node ID can't be null or undefined"); - } - return id; - }; - return this; - } - - withModifiedCheck(isModified) { - this.isModified = isModified; - return this; - } - - generateDiffTree() { - this.newMapping = this._generateIdToNodeMapping(this.tree); - this.oldMapping = this.diffWithTree ? this._generateIdToNodeMapping(this.diffWithTree) : {}; - - const diffTrees = - this._generateDiffTree(this.tree, this.diffWithTree, [], []); - - let diffTree; - if (diffTrees.length > 1) { - diffTree = { - kind: "", - name: "DiffTree", - children: diffTrees, - }; - } else { - diffTree = diffTrees[0]; - } - - return Object.freeze(diffTree); - } - - _generateIdToNodeMapping(node, acc) { - acc = acc || {}; - - const nodeId = this.getNodeId(node); - - if (acc[nodeId]) { - throw new Error(`Duplicate node id '${nodeId}' detected...`); - } - - acc[this.getNodeId(node)] = node; - - if (node.children) { - for (const child of node.children) { - this._generateIdToNodeMapping(child, acc); - } - } - - return acc; - } - - _objIsEmpty(obj) { - return Object.keys(obj).length === 0 && obj.constructor === Object; - } - - _cloneNodeWithoutChildren(node) { - const clone = {}; - - for (const key in node) { - if (key !== 'children') { - const val = node[key]; - clone[key] = isPrimitive(val) ? val : cloneDeep(val); - } - } - - return clone; - } - - _generateDiffTree(newTree, oldTree, newTreeSiblings, oldTreeSiblings) { - const diffTrees = []; - - // NOTE: A null ID represents a non existent node. - const newId = newTree ? this.getNodeId(newTree) : null; - const oldId = oldTree ? this.getNodeId(oldTree) : null; - - const newTreeSiblingIds = newTreeSiblings.map(this.getNodeId); - const oldTreeSiblingIds = oldTreeSiblings.map(this.getNodeId); - - if (newTree) { - // Deep clone newTree omitting children field - // Clone is required because trees are frozen objects — we can't - // modify the original tree object. Also means there is no side effect. - const diffTree = this._cloneNodeWithoutChildren(newTree); - - // Default to no changes - diffTree.diff = { type: DiffType.NONE }; - - if (newId !== oldId) { - // A move, addition, or deletion has occurred - let nextOldTree; - - // Check if newTree has been added or moved - if (!oldTreeSiblingIds.includes(newId)) { - if (this.oldMapping[newId]) { - // Objected existed in old tree, the counter party - // DELETED_MOVE will be/has been flagged and added to the - // diffTree when visiting it in the oldTree. - diffTree.diff = { type: DiffType.ADDED_MOVE }; - - // TODO: Figure out if oldTree is ever visited now... - - // Switch out oldTree for new one to compare against - nextOldTree = this.oldMapping[newId]; - } else { - diffTree.diff = { type: DiffType.ADDED }; - - // TODO: Figure out if oldTree is ever visited now... - - // Stop comparing against oldTree - nextOldTree = null; - } - } - - // Check if oldTree has been deleted of moved - if (oldTree && !newTreeSiblingIds.includes(oldId)) { - const deletedTreeDiff = this._cloneNodeWithoutChildren(oldTree); - - if (this.newMapping[oldId]) { - deletedTreeDiff.diff = { type: DiffType.DELETED_MOVE }; - - // Stop comparing against oldTree, will be/has been - // visited when object is seen in newTree - nextOldTree = null; - } else { - deletedTreeDiff.diff = { type: DiffType.DELETED }; - - // Visit all children to check if they have been deleted or moved - deletedTreeDiff.children = this._visitChildren(null, oldTree); - } - - diffTrees.push(deletedTreeDiff); - } - - oldTree = nextOldTree; - } else { - // TODO: Always have modified check and add modified tags on top of others - // NOTE: Doesn't check for moved and modified at the same time - if (this.isModified && this.isModified(newTree, oldTree)) { - diffTree.diff = { type: DiffType.MODIFIED }; - } - } - - diffTree.children = this._visitChildren(newTree, oldTree); - diffTrees.push(diffTree); - } else if (oldTree) { - if (!newTreeSiblingIds.includes(oldId)) { - // Deep clone oldTree omitting children field - const diffTree = this._cloneNodeWithoutChildren(oldTree); - - // newTree doesn't exists, oldTree has either been moved or deleted. - if (this.newMapping[oldId]) { - diffTree.diff = { type: DiffType.DELETED_MOVE }; - } else { - diffTree.diff = { type: DiffType.DELETED }; - } - - diffTree.children = this._visitChildren(null, oldTree); - diffTrees.push(diffTree); - } - } else { - throw new Error("Both newTree and oldTree are undefined..."); - } - - return diffTrees; - } - - _visitChildren(newTree, oldTree) { - // Recursively traverse all children of new and old tree. - const diffChildren = []; - - // TODO: Try replacing this with some sort of zipWith. - const numOfChildren = Math.max( - newTree?.children.length ?? 0, oldTree?.children.length ?? 0); - for (let i = 0; i < numOfChildren; i++) { - const newChild = i < newTree?.children.length ? - newTree.children[i] : null; - - const oldChild = i < oldTree?.children.length ? - oldTree.children[i] : null; - - const childDiffTrees = this._generateDiffTree( - newChild, oldChild, - newTree?.children ?? [], oldTree?.children ?? [] - ); - diffChildren.push(...childDiffTrees); - } - - return diffChildren; - } + return test !== Object(test); } -export { DiffGenerator, DiffType, defaultModifiedCheck } +export class DiffGenerator { + constructor(tree) { + if (tree.asRawTreeViewObject) { + this.tree = tree.asRawTreeViewObject(); + } else { + this.tree = tree; + } + } + + compareWith(tree) { + if (tree?.asRawTreeViewObject) { + this.diffWithTree = tree.asRawTreeViewObject(); + } else { + this.diffWithTree = tree; + } + + return this; + } + + withUniqueNodeId(getNodeId) { + this.getNodeId = (node) => { + const id = getNodeId(node); + if (id === null || id === undefined) { + console.error('Null node ID for node', node); + throw new Error('Node ID can\'t be null or undefined'); + } + return id; + }; + return this; + } + + withModifiedCheck(isModified) { + this.isModified = isModified; + return this; + } + + generateDiffTree() { + this.newMapping = this._generateIdToNodeMapping(this.tree); + this.oldMapping = this.diffWithTree ? + this._generateIdToNodeMapping(this.diffWithTree) : {}; + + const diffTrees = + this._generateDiffTree(this.tree, this.diffWithTree, [], []); + + let diffTree; + if (diffTrees.length > 1) { + diffTree = { + kind: '', + name: 'DiffTree', + children: diffTrees, + stableId: 'DiffTree', + }; + } else { + diffTree = diffTrees[0]; + } + + return Object.freeze(diffTree); + } + + _generateIdToNodeMapping(node, acc) { + acc = acc || {}; + + const nodeId = this.getNodeId(node); + + if (acc[nodeId]) { + throw new Error(`Duplicate node id '${nodeId}' detected...`); + } + + acc[this.getNodeId(node)] = node; + + if (node.children) { + for (const child of node.children) { + this._generateIdToNodeMapping(child, acc); + } + } + + return acc; + } + + _objIsEmpty(obj) { + return Object.keys(obj).length === 0 && obj.constructor === Object; + } + + _cloneNodeWithoutChildren(node) { + const clone = {}; + + for (const key in node) { + if (key !== 'children') { + const val = node[key]; + clone[key] = isPrimitive(val) ? val : cloneDeep(val); + } + } + + return clone; + } + + _generateDiffTree(newTree, oldTree, newTreeSiblings, oldTreeSiblings) { + const diffTrees = []; + + // NOTE: A null ID represents a non existent node. + const newId = newTree ? this.getNodeId(newTree) : null; + const oldId = oldTree ? this.getNodeId(oldTree) : null; + + const newTreeSiblingIds = newTreeSiblings.map(this.getNodeId); + const oldTreeSiblingIds = oldTreeSiblings.map(this.getNodeId); + + if (newTree) { + // Deep clone newTree omitting children field + // Clone is required because trees are frozen objects — we can't + // modify the original tree object. Also means there is no side effect. + const diffTree = this._cloneNodeWithoutChildren(newTree); + + // Default to no changes + diffTree.diff = {type: DiffType.NONE}; + + if (newId !== oldId) { + // A move, addition, or deletion has occurred + let nextOldTree; + + // Check if newTree has been added or moved + if (!oldTreeSiblingIds.includes(newId)) { + if (this.oldMapping[newId]) { + // Objected existed in old tree, the counter party + // DELETED_MOVE will be/has been flagged and added to the + // diffTree when visiting it in the oldTree. + diffTree.diff = {type: DiffType.ADDED_MOVE}; + + // TODO: Figure out if oldTree is ever visited now... + + // Switch out oldTree for new one to compare against + nextOldTree = this.oldMapping[newId]; + } else { + diffTree.diff = {type: DiffType.ADDED}; + + // TODO: Figure out if oldTree is ever visited now... + + // Stop comparing against oldTree + nextOldTree = null; + } + } + + // Check if oldTree has been deleted of moved + if (oldTree && !newTreeSiblingIds.includes(oldId)) { + const deletedTreeDiff = this._cloneNodeWithoutChildren(oldTree); + + if (this.newMapping[oldId]) { + deletedTreeDiff.diff = {type: DiffType.DELETED_MOVE}; + + // Stop comparing against oldTree, will be/has been + // visited when object is seen in newTree + nextOldTree = null; + } else { + deletedTreeDiff.diff = {type: DiffType.DELETED}; + + // Visit all children to check if they have been deleted or moved + deletedTreeDiff.children = this._visitChildren(null, oldTree); + } + + diffTrees.push(deletedTreeDiff); + } + + oldTree = nextOldTree; + } else { + // TODO: Always have modified check and add modified tags on top of + // others + // NOTE: Doesn't check for moved and modified at the same time + if (this.isModified && this.isModified(newTree, oldTree)) { + diffTree.diff = {type: DiffType.MODIFIED}; + } + } + + diffTree.children = this._visitChildren(newTree, oldTree); + diffTrees.push(diffTree); + } else if (oldTree) { + if (!newTreeSiblingIds.includes(oldId)) { + // Deep clone oldTree omitting children field + const diffTree = this._cloneNodeWithoutChildren(oldTree); + + // newTree doesn't exists, oldTree has either been moved or deleted. + if (this.newMapping[oldId]) { + diffTree.diff = {type: DiffType.DELETED_MOVE}; + } else { + diffTree.diff = {type: DiffType.DELETED}; + } + + diffTree.children = this._visitChildren(null, oldTree); + diffTrees.push(diffTree); + } + } else { + throw new Error('Both newTree and oldTree are undefined...'); + } + + return diffTrees; + } + + _visitChildren(newTree, oldTree) { + // Recursively traverse all children of new and old tree. + const diffChildren = []; + + // TODO: Try replacing this with some sort of zipWith. + const numOfChildren = Math.max( + newTree?.children.length ?? 0, oldTree?.children.length ?? 0); + for (let i = 0; i < numOfChildren; i++) { + const newChild = i < newTree?.children.length ? + newTree.children[i] : null; + + const oldChild = i < oldTree?.children.length ? + oldTree.children[i] : null; + + const childDiffTrees = this._generateDiffTree( + newChild, oldChild, + newTree?.children ?? [], oldTree?.children ?? [], + ); + diffChildren.push(...childDiffTrees); + } + + return diffChildren; + } +}
diff --git a/tools/winscope/src/utils/names.js b/tools/winscope/src/utils/names.js index abbbbce..a5ae037 100644 --- a/tools/winscope/src/utils/names.js +++ b/tools/winscope/src/utils/names.js
@@ -1,3 +1,19 @@ +/* + * 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. + */ + // Returns just the class name and root package information function getComponentClassName(componentFullName) { const classParts = componentFullName.split('.');
diff --git a/tools/winscope/src/utils/utils.js b/tools/winscope/src/utils/utils.js index 31dbfa7..f8ec198 100644 --- a/tools/winscope/src/utils/utils.js +++ b/tools/winscope/src/utils/utils.js
@@ -1,33 +1,87 @@ +/* + * 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. + */ + // Find the index of the last element matching the predicate in a sorted array function findLastMatchingSorted(array, predicate) { - let a = 0; - let b = array.length - 1; - while (b - a > 1) { - const m = Math.floor((a + b) / 2); - if (predicate(array, m)) { - a = m; - } else { - b = m - 1; - } + let a = 0; + let b = array.length - 1; + while (b - a > 1) { + const m = Math.floor((a + b) / 2); + if (predicate(array, m)) { + a = m; + } else { + b = m - 1; } + } - return predicate(array, b) ? b : a; + return predicate(array, b) ? b : a; } // Make sure stableId is unique (makes old versions of proto compatible) function stableIdCompatibilityFixup(item) { - // For backwards compatibility - // (the only item that doesn't have a unique stable ID in the tree) - if (item.stableId === 'winToken|-|') { - return item.stableId + item.children[0].stableId; - } + // For backwards compatibility + // (the only item that doesn't have a unique stable ID in the tree) + if (item.stableId === 'winToken|-|') { + return item.stableId + item.children[0].stableId; + } - return item.stableId; + return item.stableId; } const DIRECTION = Object.freeze({ - BACKWARD: -1, - FORWARD: 1, + BACKWARD: -1, + FORWARD: 1, }); -export { DIRECTION, findLastMatchingSorted, stableIdCompatibilityFixup } \ No newline at end of file +const TimeUnits = Object.freeze({ + NANO_SECONDS: 'ns', + MILLI_SECONDS: 'ms', + SECONDS: 's', + MINUTES: 'm', + HOURS: 'h', + DAYS: 'd', +}); + +function nanosToString(elapsedRealtimeNanos, precision) { + const units = [ + [1000000, TimeUnits.NANO_SECONDS], + [1000, TimeUnits.MILLI_SECONDS], + [60, TimeUnits.SECONDS], + [60, TimeUnits.MINUTES], + [24, TimeUnits.HOURS], + [Infinity, TimeUnits.DAYS], + ]; + + const parts = []; + + let precisionThresholdReached = false; + units.some(([div, timeUnit], i) => { + const part = (elapsedRealtimeNanos % div).toFixed() + if (timeUnit === precision) { + precisionThresholdReached = true; + } + if (precisionThresholdReached) { + parts.push(part + timeUnit); + } + elapsedRealtimeNanos = Math.floor(elapsedRealtimeNanos / div); + + return elapsedRealtimeNanos == 0; + }); + + return parts.reverse().join(''); +} + +export { DIRECTION, findLastMatchingSorted, stableIdCompatibilityFixup, nanosToString, TimeUnits } \ No newline at end of file
diff --git a/tools/winscope/webpack.config.common.js b/tools/winscope/webpack.config.common.js index f2fc4fa..f2faddb 100644 --- a/tools/winscope/webpack.config.common.js +++ b/tools/winscope/webpack.config.common.js
@@ -44,6 +44,9 @@ polyfill: '@babel/polyfill', main: './src/main.js', }, + externals: { + _: 'lodash', + }, resolve: { extensions: ['.tsx', '.ts', '.js', '.vue'], alias: {
diff --git a/tools/winscope/yarn.lock b/tools/winscope/yarn.lock index 679450c..238e2e1 100644 --- a/tools/winscope/yarn.lock +++ b/tools/winscope/yarn.lock
@@ -995,6 +995,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/lodash@^4.14.158": + version "4.14.158" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.158.tgz#b38ea8b6fe799acd076d7a8d7ab71c26ef77f785" + integrity sha512-InCEXJNTv/59yO4VSfuvNrZHt7eeNtWQEgnieIA+mIC+MOWM9arOWG2eQ8Vhk6NbOre6/BidiXhkZYeDY9U35w== + "@types/long@^4.0.1": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" @@ -2791,6 +2796,15 @@ memory-fs "^0.5.0" tapable "^1.0.0" +enhanced-resolve@^4.1.0, enhanced-resolve@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.2.0.tgz#5d43bda4a0fd447cb0ebbe71bef8deff8805ad0d" + integrity sha512-S7eiFb/erugyd1rLb6mQ3Vuq+EXHv5cpCkNqqIkYkBgN2QdFnyCZzFBleqwGEx4lgNGYij81BWnCrFNK7vxvjQ== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -7101,7 +7115,18 @@ micromatch "^4.0.0" semver "^6.0.0" -tslib@^1.9.0, tslib@^1.9.3: +ts-loader@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.1.tgz#9670dcbce2a8c8506d01a37fee042350d02c8c21" + integrity sha512-I9Nmly0ufJoZRMuAT9d5ijsC2B7oSPvUnOJt/GhgoATlPGYfa17VicDKPcqwUCrHpOkCxr/ybLYwbnS4cOxmvQ== + dependencies: + chalk "^2.3.0" + enhanced-resolve "^4.0.0" + loader-utils "^1.0.2" + micromatch "^4.0.0" + semver "^6.0.0" + +tslib@^1.10.0, tslib@^1.9.0, tslib@^1.9.3: version "1.13.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==