1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# Copyright (c) 2024 Huawei Device Co., Ltd.
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18
19# convert the dump result into graphical representation
20
21import os
22import shutil
23
24from typing import List
25from graphviz import Digraph
26from src.beans.event_node import EventNode
27from src.beans.event_procedures import EventProcedures
28from src.beans.event_scope import EventScope
29from src.beans.event_tree import EventTree
30from src.beans.frame_node import FrameNode
31from src.keywords import get_dict_value
32from src.utils.log_wrapper import log_message
33
34output_folder = 'output'
35# finger scope edge colors
36edge_colors = ['black', 'blue', 'brown', 'purple', 'yellow', 'pink', 'gray']
37
38
39def reset_output_dir():
40    if os.path.exists(output_folder):
41        shutil.rmtree(output_folder)
42    os.mkdir(output_folder)
43
44
45def draw_title_and_touch_points(tree: EventTree, tree_name, dot):
46    touch_points = tree.touch_points
47    current_index = 0
48    touch_points_info = f'event tree {str(tree.tree_id)} \n'
49    for touch_point in touch_points:
50        touch_points_info = f'{touch_points_info} {touch_point.to_summary_string}\n'
51        current_index += 1
52    touch_points_info.rstrip('\n')
53    if current_index != 0:
54        sub_graph = Digraph(comment='touch points')
55        sub_graph.node(tree_name, touch_points_info, shape='box')
56        dot.subgraph(sub_graph)
57
58
59class EventParentChildrenPair:
60    item_self: EventNode = None  # parent
61    children: List['EventParentChildrenPair'] = []
62
63    def __init__(self, item):
64        self.item_self = item
65        self.children = []
66
67    def append_child(self, child):
68        self.children.append(child)
69
70    def get_address(self):
71        return self.item_self.address
72
73
74def build_event_node_tree(scope: EventScope):
75    result = []
76    node_map = {}
77    flatten_frame_nodes: List[EventNode] = scope.event_nodes
78    # make a mapping table
79    for item in flatten_frame_nodes:
80        node_map[item.address] = EventParentChildrenPair(item)
81    # append child nodes to their parent's `children` attribute based on `parentId`
82    for item in flatten_frame_nodes:
83        if item.parentId is not None and item.parentId != 0 and len(item.parentId) > 6:
84            parent = get_dict_value(node_map, item.parentId)
85            if parent is not None:
86                child = get_dict_value(node_map, item.address)
87                parent.append_child(child)
88        else:
89            child = get_dict_value(node_map, item.address)
90            result.append(child)
91    return result
92
93
94# draw node relationships recursively
95def draw_event_scop_tree_recursively(node_tree: List[EventParentChildrenPair],
96                                     parent_node_name: str,
97                                     finger,
98                                     graph: Digraph,
99                                     is_show_detail):
100    for item in node_tree:
101        node_name = item.get_address()
102        node_label = item.item_self.get_summary_string()
103        if is_show_detail:
104            node_label = item.item_self.get_detailed_summary_string()
105        graph.node(node_name, node_label, tooltip=item.item_self.to_string())
106        if parent_node_name is not None:
107            graph.edge(parent_node_name, node_name, color=edge_colors[finger])
108        if len(item.children) > 0:
109            draw_event_scop_tree_recursively(item.children, node_name, finger, graph, is_show_detail)
110
111
112def draw_event_procedures(tree: EventTree, tree_name, dot, is_show_detail):
113    event_procedures: EventProcedures = tree.event_procedures
114    if event_procedures is None:
115        return
116    tag = f'{str(tree.tree_id)} event procedures'
117    sub_graph = Digraph(comment=tag)
118    current_index = 0
119    for scope in event_procedures.event_scopes:
120        comment = f'event scope {str(scope.finger)}'
121        sub_scope_graph = Digraph(comment=comment)
122        node_tree = build_event_node_tree(scope)
123        # treat finger as root node of subgraph
124        scope_root_node_name = f'finger {str(scope.finger)}'
125        sub_scope_graph.node(scope_root_node_name, scope_root_node_name)
126        dot.edge(tree_name, scope_root_node_name, color=edge_colors[current_index])
127        draw_event_scop_tree_recursively(node_tree, scope_root_node_name, current_index, sub_scope_graph,
128                                         is_show_detail)
129        sub_graph.subgraph(sub_scope_graph)
130        current_index += 1
131    dot.subgraph(sub_graph)
132
133
134def generate_event_trees_graph(dump_result, is_show_detail):
135    current_index = 0
136    # draw every event tree into file
137    for tree in dump_result.event_trees:
138        # create a graph
139        comment = f'event dump {str(current_index)}'
140        dot = Digraph(comment=comment)
141        # draw touch points info
142        tree_name = f'event tree {str(tree.tree_id)}'
143        draw_title_and_touch_points(tree, tree_name, dot)
144        # draw event procedures
145        draw_event_procedures(tree, tree_name, dot, is_show_detail)
146        # save to file
147        file_name = f'event_tree_{str(tree.tree_id)}'
148        out_graph_file_name = os.path.join(output_folder, file_name)
149        dot.render(out_graph_file_name, format='svg', cleanup=True, view=False)
150        current_index += 1
151    log_message('event trees graph generated done, count: ' + str(current_index))
152
153
154class FrameNodeParentChildrenPair:
155    item_self: FrameNode = None  # parent
156    children: List['FrameNodeParentChildrenPair'] = []
157
158    def __init__(self, item):
159        self.item_self = item
160        self.children = []
161
162    def append_child(self, child):
163        self.children.append(child)
164
165    def get_node_id(self):
166        return self.item_self.nodeId
167
168
169def build_hittest_result_tree(tree: EventTree):
170    result = []
171    node_map = {}
172    flatten_frame_nodes: List[FrameNode] = tree.frame_nodes
173    # make a mapping table
174    for item in flatten_frame_nodes:
175        node_map[item.nodeId] = FrameNodeParentChildrenPair(item)
176    # # append child nodes to their parent's `children` attribute based on `parentId`
177    for item in flatten_frame_nodes:
178        if item.parentId is not None and item.parentId != -1:
179            parent = get_dict_value(node_map, item.parentId)
180            if parent is not None:
181                child = get_dict_value(node_map, item.nodeId)
182                parent.append_child(child)
183        else:
184            child = get_dict_value(node_map, item.nodeId)
185            result.append(child)
186    return result
187
188
189def generate_hittest_label_with_highlight(item: FrameNode):
190    if item.isHit == 0:
191        return item.get_showup_string()
192
193    label = '<{}({})<br/><font color="red">isHit: {}</font><br/>hitTestMode: {} >'.format(item.tag, item.nodeId,
194                                                                                          item.isHit,
195                                                                                          item.hitTestMode)
196    return label
197
198
199def draw_hittest_result_recursively(node_tree: List[FrameNodeParentChildrenPair], parent_node_name: str,
200                                    graph: Digraph):
201    for item in node_tree:
202        node_name = 'frame node ' + str(item.get_node_id())
203        node_label = generate_hittest_label_with_highlight(item.item_self)
204        graph.node(node_name, node_label, tooltip=item.item_self.to_string())
205        if parent_node_name is not None:
206            graph.edge(parent_node_name, node_name)
207        if len(item.children) > 0:
208            draw_hittest_result_recursively(item.children, node_name, graph)
209
210
211def draw_hittest_result(tree: EventTree, tree_name, dot):
212    hittest_result = build_hittest_result_tree(tree)
213    draw_hittest_result_recursively(hittest_result, tree_name, dot)
214
215
216def generate_hittest_graph(dump_result):
217    current_index = 0
218    # draw every event tree into file
219    for tree in dump_result.event_trees:
220        # create a graph
221        dot = Digraph(comment='hit test result ' + str(current_index))
222        tree_name = 'hit test result ' + str(tree.tree_id)
223        # draw event procedures
224        draw_hittest_result(tree, tree_name, dot)
225        # save to file
226        file_name = f'hit_test_{str(tree.tree_id)}'
227        out_graph_file_name = os.path.join(output_folder, file_name)
228        dot.render(out_graph_file_name, format='svg', cleanup=True, view=False)
229        current_index += 1
230    log_message('hit test graph generated done, count: ' + str(current_index))
231
232
233def generate_all_graphs(dump_result, is_show_detail):
234    # delete all history files before generate new ones
235    reset_output_dir()
236    generate_event_trees_graph(dump_result, is_show_detail)
237    generate_hittest_graph(dump_result)
238