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