1/* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import {Analytics} from 'logging/analytics'; 18import {TRACE_INFO} from 'trace/trace_info'; 19import {TraceType} from 'trace/trace_type'; 20import {HierarchyTreeNode} from 'trace/tree_node/hierarchy_tree_node'; 21import {Operation} from 'trace/tree_node/operations/operation'; 22import {PropertyTreeNode} from 'trace/tree_node/property_tree_node'; 23import {TreeNode} from 'trace/tree_node/tree_node'; 24import {TextFilter} from 'viewers/common/text_filter'; 25import {IsModifiedCallbackType} from './add_diffs'; 26import {AddDiffsPropertiesTree} from './add_diffs_properties_tree'; 27import {Filter} from './operations/filter'; 28import {UiPropertyTreeNode} from './ui_property_tree_node'; 29import {UiTreeFormatter} from './ui_tree_formatter'; 30import {TreeNodeFilter, UiTreeUtils} from './ui_tree_utils'; 31import {UserOptions} from './user_options'; 32 33export class PropertiesPresenter { 34 private propertiesFilter: TreeNodeFilter; 35 private highlightedProperty = ''; 36 private propertiesTree: PropertyTreeNode | undefined; 37 private formattedTree: UiPropertyTreeNode | undefined; 38 39 constructor( 40 private userOptions: UserOptions, 41 private textFilter: TextFilter, 42 private propertiesDenylist: string[], 43 private customOperations?: Array<Operation<UiPropertyTreeNode>>, 44 private defaultAllowlist: string[] = [], 45 ) { 46 this.propertiesFilter = UiTreeUtils.makeNodeFilter( 47 this.textFilter.getFilterPredicate(), 48 ); 49 } 50 51 getUserOptions(): UserOptions { 52 return this.userOptions; 53 } 54 55 setPropertiesTree(tree: PropertyTreeNode | undefined) { 56 this.propertiesTree = tree; 57 } 58 59 getPropertiesTree(): PropertyTreeNode | undefined { 60 return this.propertiesTree; 61 } 62 63 getFormattedTree(): UiPropertyTreeNode | undefined { 64 return this.formattedTree; 65 } 66 67 getHighlightedProperty(): string { 68 return this.highlightedProperty; 69 } 70 71 applyHighlightedPropertyChange(id: string) { 72 if (this.highlightedProperty === id) { 73 this.highlightedProperty = ''; 74 } else { 75 this.highlightedProperty = id; 76 } 77 } 78 79 getTextFilter(): TextFilter | undefined { 80 return this.textFilter; 81 } 82 83 applyPropertiesFilterChange(textFilter: TextFilter) { 84 this.textFilter = textFilter; 85 this.propertiesFilter = UiTreeUtils.makeNodeFilter( 86 textFilter.getFilterPredicate(), 87 ); 88 } 89 90 applyPropertiesUserOptionsChange(userOptions: UserOptions) { 91 this.userOptions = userOptions; 92 } 93 94 updateDefaultAllowList(value: string[]) { 95 this.defaultAllowlist = value; 96 } 97 98 async formatPropertiesTree( 99 previousHierarchyTree: HierarchyTreeNode | undefined, 100 displayName: string | undefined, 101 keepCalculated: boolean, 102 traceType?: TraceType, 103 ): Promise<void> { 104 if (!this.propertiesTree) { 105 this.formattedTree = undefined; 106 return; 107 } 108 const uiTree = UiPropertyTreeNode.from(this.propertiesTree); 109 110 if ( 111 this.userOptions['showDiff']?.enabled && 112 !this.userOptions['showDiff']?.isUnavailable 113 ) { 114 const prevEntryNode = previousHierarchyTree?.findDfs( 115 UiTreeUtils.makeIdMatchFilter(this.propertiesTree.id), 116 ); 117 const prevEntryUiTree = prevEntryNode 118 ? UiPropertyTreeNode.from(await prevEntryNode.getAllProperties()) 119 : undefined; 120 121 const startTimeMs = Date.now(); 122 await new AddDiffsPropertiesTree( 123 PropertiesPresenter.isPropertyNodeModified, 124 this.propertiesDenylist, 125 ).executeInPlace(uiTree, prevEntryUiTree); 126 Analytics.Navigation.logDiffComputationTime( 127 'properties', 128 traceType ? TRACE_INFO[traceType].name : 'Unknown', 129 Date.now() - startTimeMs, 130 ); 131 } 132 133 if (displayName) { 134 uiTree.setDisplayName(displayName); 135 } 136 137 const predicatesKeepingChildren = [this.propertiesFilter]; 138 const predicatesDiscardingChildren = []; 139 140 if (this.propertiesDenylist) { 141 predicatesDiscardingChildren.push( 142 UiTreeUtils.makeDenyListFilterByName(this.propertiesDenylist), 143 ); 144 } 145 146 if (!this.userOptions['showDefaults']?.enabled) { 147 predicatesDiscardingChildren.push( 148 UiTreeUtils.makeIsNotDefaultFilter(this.defaultAllowlist), 149 ); 150 } 151 152 if (!keepCalculated) { 153 predicatesDiscardingChildren.push(UiTreeUtils.isNotCalculated); 154 } 155 const formatter = new UiTreeFormatter<UiPropertyTreeNode>().setUiTree( 156 uiTree, 157 ); 158 if (predicatesDiscardingChildren.length > 0) { 159 formatter.addOperation(new Filter(predicatesDiscardingChildren, false)); 160 } 161 formatter.addOperation(new Filter(predicatesKeepingChildren, true)); 162 163 this.customOperations?.forEach((op) => formatter.addOperation(op)); 164 165 this.formattedTree = formatter.format(); 166 } 167 168 clear() { 169 this.propertiesTree = undefined; 170 this.formattedTree = undefined; 171 } 172 173 static isPropertyNodeModified: IsModifiedCallbackType = async ( 174 newTree: TreeNode, 175 oldTree: TreeNode, 176 ) => { 177 const newValue = (newTree as UiPropertyTreeNode).formattedValue(); 178 const oldValue = (oldTree as UiPropertyTreeNode).formattedValue(); 179 180 return oldValue !== newValue; 181 }; 182} 183