• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package com.android.launcher3.util.viewcapture_analysis;
17 
18 import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode;
19 
20 import java.util.List;
21 
22 /**
23  * Anomaly detector that triggers an error when alpha of a view changes too rapidly.
24  * Invisible views are treated as if they had zero alpha.
25  */
26 final class AlphaJumpDetector extends AnomalyDetector {
27     // Commonly used parts of the paths to ignore.
28     private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|";
29     private static final String DRAG_LAYER =
30             CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|";
31     private static final String RECENTS_DRAG_LAYER =
32             CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|";
33 
34     private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of(
35             CONTENT
36                     + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
37                     + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
38                     + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell"
39                     + "|WidgetCellPreview:id/widget_preview_container|ImageView:id/widget_badge",
40             CONTENT
41                     + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
42                     + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
43                     + "|ScrollView:id/widget_preview_scroll_view|WidgetCell:id/widget_cell"
44                     + "|WidgetCellPreview:id/widget_preview_container|WidgetCell$1|FrameLayout"
45                     + "|ImageView:id/icon",
46             CONTENT + "AddItemDragLayer:id/add_item_drag_layer|View",
47             DRAG_LAYER
48                     + "AppWidgetResizeFrame|FrameLayout|ImageButton:id/widget_reconfigure_button",
49             DRAG_LAYER
50                     + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_bottom_handle",
51             DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_frame",
52             DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_left_handle",
53             DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_right_handle",
54             DRAG_LAYER + "AppWidgetResizeFrame|FrameLayout|ImageView:id/widget_resize_top_handle",
55             DRAG_LAYER + "FloatingTaskView|FloatingTaskThumbnailView:id/thumbnail",
56             DRAG_LAYER + "FloatingTaskView|SplitPlaceholderView:id/split_placeholder",
57             DRAG_LAYER + "Folder|FolderPagedView:id/folder_content",
58             DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view",
59             DRAG_LAYER + "LauncherDragView",
60             DRAG_LAYER + "LauncherRecentsView:id/overview_panel",
61             DRAG_LAYER
62                     + "NexusOverviewActionsView:id/overview_actions_view|FrameLayout:id"
63                     + "/select_mode_buttons|ImageButton:id/close",
64             DRAG_LAYER
65                     + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
66                     + "/action_buttons|Button:id/action_screenshot",
67             DRAG_LAYER
68                     + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
69                     + "/action_buttons|Button:id/action_select",
70             DRAG_LAYER
71                     + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
72                     + "/action_buttons|Button:id/action_split",
73             DRAG_LAYER
74                     + "NexusOverviewActionsView:id/overview_actions_view|LinearLayout:id"
75                     + "/action_buttons|Space:id/action_split_space",
76             DRAG_LAYER
77                     + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
78                     + "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material"
79                     + "|DeepShortcutTextView:id/bubble_text",
80             DRAG_LAYER
81                     + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
82                     + "/deep_shortcuts_container|DeepShortcutView:id/deep_shortcut_material|View"
83                     + ":id/icon",
84             DRAG_LAYER
85                     + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
86                     + "/system_shortcuts_container|DeepShortcutView:id/system_shortcut"
87                     + "|BubbleTextView:id/bubble_text",
88             DRAG_LAYER
89                     + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
90                     + "/system_shortcuts_container|DeepShortcutView:id/system_shortcut|View:id"
91                     + "/icon",
92             DRAG_LAYER
93                     + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
94                     + "/system_shortcuts_container|ImageView",
95             DRAG_LAYER
96                     + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
97                     + "/widget_shortcut_container|DeepShortcutView:id/system_shortcut"
98                     + "|BubbleTextView:id/bubble_text",
99             DRAG_LAYER
100                     + "PopupContainerWithArrow:id/popup_container|LinearLayout:id"
101                     + "/widget_shortcut_container|DeepShortcutView:id/system_shortcut|View:id/icon",
102             DRAG_LAYER + "SearchContainerView:id/apps_view",
103             DRAG_LAYER + "Snackbar|TextView:id/action",
104             DRAG_LAYER + "Snackbar|TextView:id/label",
105             DRAG_LAYER + "SplitInstructionsView|AppCompatTextView:id/split_instructions_text",
106             DRAG_LAYER + "TaskMenuView|LinearLayout:id/menu_option_layout",
107             DRAG_LAYER + "TaskMenuViewWithArrow|LinearLayout:id/menu_option_layout",
108             DRAG_LAYER + "TaskMenuView|TextView:id/task_name",
109             DRAG_LAYER + "View",
110             DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container",
111             DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container",
112             CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
113             RECENTS_DRAG_LAYER + "ArrowTipView",
114             DRAG_LAYER + "ArrowTipView",
115             DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
116             RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel",
117             DRAG_LAYER
118                     + "NexusOverviewActionsView:id/overview_actions_view"
119                     + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
120             RECENTS_DRAG_LAYER
121                     + "NexusOverviewActionsView:id/overview_actions_view"
122                     + "|LinearLayout:id/action_buttons|Button:id/action_screenshot",
123             DRAG_LAYER
124                     + "NexusOverviewActionsView:id/overview_actions_view"
125                     + "|LinearLayout:id/action_buttons|Button:id/action_select",
126             RECENTS_DRAG_LAYER
127                     + "NexusOverviewActionsView:id/overview_actions_view"
128                     + "|LinearLayout:id/action_buttons|Button:id/action_select",
129             DRAG_LAYER
130                     + "NexusOverviewActionsView:id/overview_actions_view"
131                     + "|LinearLayout:id/action_buttons|Button:id/action_split",
132             RECENTS_DRAG_LAYER
133                     + "NexusOverviewActionsView:id/overview_actions_view"
134                     + "|LinearLayout:id/action_buttons|Button:id/action_split",
135             DRAG_LAYER + "IconView"
136     ));
137 
138     // Minimal increase or decrease of view's alpha between frames that triggers the error.
139     private static final float ALPHA_JUMP_THRESHOLD = 1f;
140 
141     // Per-AnalysisNode data that's specific to this detector.
142     private static class NodeData {
143         public boolean ignoreAlphaJumps;
144 
145         // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is
146         // ignored.
147         // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no
148         // children.
149         public IgnoreNode ignoreNode;
150     }
151 
getNodeData(AnalysisNode info)152     private NodeData getNodeData(AnalysisNode info) {
153         return (NodeData) info.detectorsData[detectorOrdinal];
154     }
155 
156     @Override
initializeNode(AnalysisNode info)157     void initializeNode(AnalysisNode info) {
158         final NodeData nodeData = new NodeData();
159         info.detectorsData[detectorOrdinal] = nodeData;
160 
161         // If the parent view ignores alpha jumps, its descendants will too.
162         final boolean parentIgnoresAlphaJumps = info.parent != null && getNodeData(
163                 info.parent).ignoreAlphaJumps;
164         if (parentIgnoresAlphaJumps) {
165             nodeData.ignoreAlphaJumps = true;
166             return;
167         }
168 
169         // Parent view doesn't ignore alpha jumps.
170         // Initialize this AnalysisNode's ignore-node with the corresponding child of the
171         // ignore-node of the parent, if present.
172         final IgnoreNode parentIgnoreNode = info.parent != null
173                 ? getNodeData(info.parent).ignoreNode
174                 : IGNORED_NODES_ROOT;
175         nodeData.ignoreNode = parentIgnoreNode != null
176                 ? parentIgnoreNode.children.get(info.nodeIdentity) : null;
177         // AnalysisNode will be ignored if the corresponding ignore-node is a leaf.
178         nodeData.ignoreAlphaJumps =
179                 nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty();
180     }
181 
182     @Override
detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp, int windowSizePx)183     String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp,
184             int windowSizePx) {
185         // If the view was previously seen, proceed with analysis only if it was present in the
186         // view hierarchy in the previous frame.
187         if (oldInfo != null && oldInfo.frameN != frameN) return null;
188 
189         final AnalysisNode latestInfo = newInfo != null ? newInfo : oldInfo;
190         final NodeData nodeData = getNodeData(latestInfo);
191         if (nodeData.ignoreAlphaJumps) return null;
192 
193         final float oldAlpha = oldInfo != null ? oldInfo.alpha : 0;
194         final float newAlpha = newInfo != null ? newInfo.alpha : 0;
195         final float alphaDeltaAbs = Math.abs(newAlpha - oldAlpha);
196 
197         if (alphaDeltaAbs >= ALPHA_JUMP_THRESHOLD) {
198             nodeData.ignoreAlphaJumps = true; // No need to report alpha jump in children.
199             return String.format(
200                     "Alpha jump detected: alpha change: %s (%s -> %s), threshold: %s",
201                     alphaDeltaAbs, oldAlpha, newAlpha, ALPHA_JUMP_THRESHOLD);
202         }
203         return null;
204     }
205 }
206