blob: 326f8426707bdf3d152ddbcb75da98219227ff8d [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { UiData } from "./ui_data";
import { Rectangle, RectMatrix, RectTransform } from "viewers/common/rectangle";
import { TraceType } from "common/trace/trace_type";
import { TraceTreeNode } from "common/trace/trace_tree_node";
import { TreeUtils, FilterType } from "common/utils/tree_utils";
import { UserOptions } from "viewers/common/user_options";
import { HierarchyTreeNode, PropertiesTreeNode } from "viewers/common/ui_tree_utils";
import { TreeGenerator } from "viewers/common/tree_generator";
import { TreeTransformer } from "viewers/common/tree_transformer";
import DisplayContent from "common/trace/flickerlib/windows/DisplayContent";
import { PersistentStoreObject } from "common/utils/persistent_store_object";
type NotifyViewCallbackType = (uiData: UiData) => void;
export class Presenter {
constructor(notifyViewCallback: NotifyViewCallbackType, private storage: Storage) {
this.notifyViewCallback = notifyViewCallback;
this.uiData = new UiData([TraceType.WINDOW_MANAGER]);
this.notifyViewCallback(this.uiData);
}
public updatePinnedItems(pinnedItem: HierarchyTreeNode) {
const pinnedId = `${pinnedItem.id}`;
if (this.pinnedItems.map(item => `${item.id}`).includes(pinnedId)) {
this.pinnedItems = this.pinnedItems.filter(pinned => `${pinned.id}` != pinnedId);
} else {
this.pinnedItems.push(pinnedItem);
}
this.updatePinnedIds(pinnedId);
this.uiData.pinnedItems = this.pinnedItems;
this.notifyViewCallback(this.uiData);
}
public updateHighlightedItems(id: string) {
if (this.highlightedItems.includes(id)) {
this.highlightedItems = this.highlightedItems.filter(hl => hl != id);
} else {
this.highlightedItems = []; //if multi-select implemented, remove this line
this.highlightedItems.push(id);
}
this.uiData.highlightedItems = this.highlightedItems;
this.notifyViewCallback(this.uiData);
}
public updateHierarchyTree(userOptions: UserOptions) {
this.hierarchyUserOptions = userOptions;
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.tree = this.generateTree();
this.notifyViewCallback(this.uiData);
}
public filterHierarchyTree(filterString: string) {
this.hierarchyFilter = TreeUtils.makeNodeFilter(filterString);
this.uiData.tree = this.generateTree();
this.notifyViewCallback(this.uiData);
}
public updatePropertiesTree(userOptions: UserOptions) {
this.propertiesUserOptions = userOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
this.updateSelectedTreeUiData();
}
public filterPropertiesTree(filterString: string) {
this.propertiesFilter = TreeUtils.makeNodeFilter(filterString);
this.updateSelectedTreeUiData();
}
public newPropertiesTree(selectedTree: HierarchyTreeNode) {
this.selectedHierarchyTree = selectedTree;
this.updateSelectedTreeUiData();
}
public notifyCurrentTraceEntries(entries: Map<TraceType, [any, any]>) {
this.uiData = new UiData();
this.uiData.hierarchyUserOptions = this.hierarchyUserOptions;
this.uiData.propertiesUserOptions = this.propertiesUserOptions;
const wmEntries = entries.get(TraceType.WINDOW_MANAGER);
if (wmEntries) {
[this.entry, this.previousEntry] = wmEntries;
if (this.entry) {
this.uiData.highlightedItems = this.highlightedItems;
this.uiData.rects = this.generateRects();
this.uiData.displayIds = this.displayIds;
this.uiData.tree = this.generateTree();
}
}
this.notifyViewCallback(this.uiData);
}
private generateRects(): Rectangle[] {
const displayRects = this.entry?.displays?.map((display: DisplayContent) => {
const rect = display.displayRect;
rect.label = `Display - ${display.title}`;
rect.id = display.layerId;
rect.displayId = display.id;
rect.isDisplay = true;
rect.isVirtual = false;
return rect;
}) ?? [];
this.displayIds = [];
const rects = this.entry?.windowStates
?.sort((a: any, b: any) => b.computedZ - a.computedZ)
.map((it: any) => {
const rect = it.rect;
rect.id = it.layerId;
rect.displayId = it.displayId;
if (!this.displayIds.includes(it.displayId)) {
this.displayIds.push(it.displayId);
}
return rect;
}) ?? [];
return this.rectsToUiData(rects.concat(displayRects));
}
private updateSelectedTreeUiData() {
if (this.selectedHierarchyTree) {
this.uiData.propertiesTree = this.getTreeWithTransformedProperties(this.selectedHierarchyTree);
}
this.notifyViewCallback(this.uiData);
}
private generateTree() {
if (!this.entry) {
return null;
}
const generator = new TreeGenerator(this.entry, this.hierarchyFilter, this.pinnedIds)
.setIsOnlyVisibleView(this.hierarchyUserOptions["onlyVisible"]?.enabled)
.setIsSimplifyNames(this.hierarchyUserOptions["simplifyNames"]?.enabled)
.setIsFlatView(this.hierarchyUserOptions["flat"]?.enabled)
.withUniqueNodeId();
let tree: HierarchyTreeNode | null;
if (!this.hierarchyUserOptions["showDiff"]?.enabled) {
tree = generator.generateTree();
} else {
tree = generator.compareWith(this.previousEntry)
.withModifiedCheck()
.generateFinalTreeWithDiff();
}
this.pinnedItems = generator.getPinnedItems();
this.uiData.pinnedItems = this.pinnedItems;
return tree;
}
private rectsToUiData(rects: any[]): Rectangle[] {
const uiRects: Rectangle[] = [];
const identityMatrix: RectMatrix = {
dsdx: 1,
dsdy: 0,
tx: 0,
dtdx: 0,
dtdy: 1,
ty: 0
};
rects.forEach((rect: any) => {
const transform: RectTransform = {
matrix: identityMatrix,
};
const newRect: Rectangle = {
topLeft: {x: rect.left, y: rect.top},
bottomRight: {x: rect.right, y: rect.bottom},
height: rect.height,
width: rect.width,
label: rect.label,
transform: transform,
isVisible: rect.ref?.isVisible ?? false,
isDisplay: rect.isDisplay ?? false,
ref: rect.ref,
id: rect.id ?? rect.ref.id,
displayId: rect.displayId ?? rect.ref.stackId,
isVirtual: rect.isVirtual ?? false,
isClickable: !rect.isDisplay,
};
uiRects.push(newRect);
});
return uiRects;
}
private updatePinnedIds(newId: string) {
if (this.pinnedIds.includes(newId)) {
this.pinnedIds = this.pinnedIds.filter(pinned => pinned != newId);
} else {
this.pinnedIds.push(newId);
}
}
private getTreeWithTransformedProperties(selectedTree: HierarchyTreeNode): PropertiesTreeNode {
if (!this.entry) {
return {};
}
const transformer = new TreeTransformer(selectedTree, this.propertiesFilter)
.setOnlyProtoDump(true)
.setIsShowDefaults(this.propertiesUserOptions["showDefaults"]?.enabled)
.setIsShowDiff(this.propertiesUserOptions["showDiff"]?.enabled)
.setTransformerOptions({skip: selectedTree.skip})
.setProperties(this.entry)
.setDiffProperties(this.previousEntry);
const transformedTree = transformer.transform();
return transformedTree;
}
private readonly notifyViewCallback: NotifyViewCallbackType;
private uiData: UiData;
private hierarchyFilter: FilterType = TreeUtils.makeNodeFilter("");
private propertiesFilter: FilterType = TreeUtils.makeNodeFilter("");
private highlightedItems: Array<string> = [];
private displayIds: Array<number> = [];
private pinnedItems: Array<HierarchyTreeNode> = [];
private pinnedIds: Array<string> = [];
private selectedHierarchyTree: HierarchyTreeNode | null = null;
private previousEntry: TraceTreeNode | null = null;
private entry: TraceTreeNode | null = null;
private hierarchyUserOptions: UserOptions = PersistentStoreObject.new<UserOptions>("WmHierarchyOptions", {
showDiff: {
name: "Show diff",
enabled: false
},
simplifyNames: {
name: "Simplify names",
enabled: true
},
onlyVisible: {
name: "Only visible",
enabled: false
},
flat: {
name: "Flat",
enabled: false
}
}, this.storage);
private propertiesUserOptions: UserOptions = PersistentStoreObject.new<UserOptions>("WmPropertyOptions", {
showDiff: {
name: "Show diff",
enabled: false
},
showDefaults: {
name: "Show defaults",
enabled: false,
tooltip: `
If checked, shows the value of all properties.
Otherwise, hides all properties whose value is
the default for its data type.
`
},
}, this.storage);
}