Loading tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java +2 −1 Original line number Diff line number Diff line Loading @@ -180,7 +180,8 @@ final class AlphaJumpDetector extends AnomalyDetector { } @Override String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp) { String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp, int windowSizePx) { // If the view was previously seen, proceed with analysis only if it was present in the // view hierarchy in the previous frame. if (oldInfo != null && oldInfo.frameN != frameN) return null; Loading tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java +9 −8 Original line number Diff line number Diff line Loading @@ -72,13 +72,14 @@ abstract class AnomalyDetector { * hierarchy before 'currentFrame'. 'null' means that the view is first seen * in the 'currentFrame'. * @param newInfo the view in the view hierarchy of the 'currentFrame'. 'null' means that * the view is not present in the 'currentFrame', but was present in the previous * frame. * the view is not present in the 'currentFrame', but was present in the * previous frame. * @param frameN number of the current frame. * @param windowSizePx maximum of the window width and height, in pixels. * @return Anomaly diagnostic message if an anomaly has been detected; null otherwise. */ abstract String detectAnomalies( @Nullable ViewCaptureAnalyzer.AnalysisNode oldInfo, @Nullable ViewCaptureAnalyzer.AnalysisNode newInfo, int frameN, long frameTimeNs); long frameTimeNs, int windowSizePx); } tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java +1 −1 Original line number Diff line number Diff line Loading @@ -110,7 +110,7 @@ final class FlashDetector extends AnomalyDetector { @Override String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long frameTimeNs) { long frameTimeNs, int windowSizePx) { // Should we check when a view was visible for a short period, then its alpha became 0? // Then 'lastVisible' time should be the last one still visible? // Check only transitions of alpha between 0 and 1? Loading tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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. */ package com.android.launcher3.util.viewcapture_analysis; import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode; import java.util.List; /** * Anomaly detector that triggers an error when a view position jumps. */ final class PositionJumpDetector extends AnomalyDetector { // Maximum allowed jump in "milliwindows", i.e. a 1/1000's of the maximum of the window // dimensions. private static final float JUMP_MIW = 250; private static final String[] BORDER_NAMES = {"left", "top", "right", "bottom"}; // Commonly used parts of the paths to ignore. private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|"; private static final String DRAG_LAYER = CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|"; private static final String RECENTS_DRAG_LAYER = CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|"; private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of( DRAG_LAYER + "SearchContainerView:id/apps_view", DRAG_LAYER + "AppWidgetResizeFrame", DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view", CONTENT + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content", DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container", DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container", DRAG_LAYER + "LauncherDragView", RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView", CONTENT + "LauncherRootView:id/launcher|FloatingIconView", DRAG_LAYER + "FloatingTaskView", DRAG_LAYER + "LauncherRecentsView:id/overview_panel" )); // Per-AnalysisNode data that's specific to this detector. private static class NodeData { public boolean ignoreJumps; // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is // ignored. // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no // children. public IgnoreNode ignoreNode; } private NodeData getNodeData(AnalysisNode info) { return (NodeData) info.detectorsData[detectorOrdinal]; } @Override void initializeNode(AnalysisNode info) { final NodeData nodeData = new NodeData(); info.detectorsData[detectorOrdinal] = nodeData; // If the parent view ignores jumps, its descendants will too. final boolean parentIgnoresJumps = info.parent != null && getNodeData( info.parent).ignoreJumps; if (parentIgnoresJumps) { nodeData.ignoreJumps = true; return; } // Parent view doesn't ignore jumps. // Initialize this AnalysisNode's ignore-node with the corresponding child of the // ignore-node of the parent, if present. final IgnoreNode parentIgnoreNode = info.parent != null ? getNodeData(info.parent).ignoreNode : IGNORED_NODES_ROOT; nodeData.ignoreNode = parentIgnoreNode != null ? parentIgnoreNode.children.get(info.nodeIdentity) : null; // AnalysisNode will be ignored if the corresponding ignore-node is a leaf. nodeData.ignoreJumps = nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty(); } @Override String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long frameTimeNs, int windowSizePx) { // If the view is not present in the current frame, there can't be a jump detected in the // current frame. if (newInfo == null) return null; // We only detect position jumps if the view was visible in the previous frame. if (oldInfo == null || frameN != oldInfo.frameN + 1) return null; final NodeData newNodeData = getNodeData(newInfo); if (newNodeData.ignoreJumps) return null; final float[] positionDiffs = { newInfo.left - oldInfo.left, newInfo.top - oldInfo.top, newInfo.right - oldInfo.right, newInfo.bottom - oldInfo.bottom }; for (int i = 0; i < 4; ++i) { final float positionDiffAbs = Math.abs(positionDiffs[i]); if (positionDiffAbs * 1000 > JUMP_MIW * windowSizePx) { newNodeData.ignoreJumps = true; return String.format("Position jump: %s jumped by %s", BORDER_NAMES[i], positionDiffAbs); } } return null; } } tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java +47 −16 Original line number Diff line number Diff line Loading @@ -36,7 +36,8 @@ public class ViewCaptureAnalyzer { // All detectors. They will be invoked in the order listed here. private static final AnomalyDetector[] ANOMALY_DETECTORS = { new AlphaJumpDetector(), new FlashDetector() new FlashDetector(), new PositionJumpDetector() }; static { Loading @@ -52,6 +53,8 @@ public class ViewCaptureAnalyzer { // Window coordinates of the view. public float left; public float top; public float right; public float bottom; // Visible scale and alpha, build recursively from the ancestor list. public float scaleX; Loading Loading @@ -81,7 +84,8 @@ public class ViewCaptureAnalyzer { @Override public String toString() { return String.format("view window coordinates: (%s, %s)", left, top); return String.format("view window coordinates: (%s, %s, %s, %s)", left, top, right, bottom); } } Loading Loading @@ -112,15 +116,33 @@ public class ViewCaptureAnalyzer { // As we go though frames, if a view becomes invisible, it stays in the map. final Map<Integer, AnalysisNode> lastSeenNodes = new HashMap<>(); int windowWidthPx = -1; int windowHeightPx = -1; for (int frameN = 0; frameN < windowData.getFrameDataCount(); ++frameN) { analyzeFrame(frameN, windowData.getFrameData(frameN), viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies); final FrameData frame = windowData.getFrameData(frameN); final ViewNode rootNode = frame.getNode(); // If the rotation or window size has changed, reset the analyzer state. final boolean isFirstFrame = windowWidthPx != rootNode.getWidth() || windowHeightPx != rootNode.getHeight(); if (isFirstFrame) { windowWidthPx = rootNode.getWidth(); windowHeightPx = rootNode.getHeight(); lastSeenNodes.clear(); } final int windowSizePx = Math.max(rootNode.getWidth(), rootNode.getHeight()); analyzeFrame(frameN, isFirstFrame, frame, viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies, windowSizePx); } } private static void analyzeFrame(int frameN, FrameData frame, ExportedData viewCaptureData, private static void analyzeFrame(int frameN, boolean isFirstFrame, FrameData frame, ExportedData viewCaptureData, Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex, Map<String, String> anomalies) { Map<String, String> anomalies, int windowSizePx) { // Analyze the node tree starting from the root. long frameTimeNs = frame.getTimestamp(); analyzeView( Loading @@ -128,12 +150,14 @@ public class ViewCaptureAnalyzer { frame.getNode(), /* parent = */ null, frameN, isFirstFrame, /* leftShift = */ 0, /* topShift = */ 0, viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies); anomalies, windowSizePx); // Analyze transitions when a view visible in the previous frame became invisible in the // current one. Loading @@ -148,7 +172,8 @@ public class ViewCaptureAnalyzer { /* oldInfo = */ info, /* newInfo = */ null, anomalies, frameTimeNs) frameTimeNs, windowSizePx) ); } info.timeBecameInvisibleNs = info.alpha == 1 ? frameTimeNs : -1; Loading @@ -159,9 +184,9 @@ public class ViewCaptureAnalyzer { private static void analyzeView(long frameTimeNs, ViewNode viewCaptureNode, AnalysisNode parent, int frameN, float leftShift, float topShift, ExportedData viewCaptureData, boolean isFirstFrame, float leftShift, float topShift, ExportedData viewCaptureData, Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex, Map<String, String> anomalies) { Map<String, String> anomalies, int windowSizePx) { // Skip analysis of invisible views final float parentAlpha = parent != null ? parent.alpha : 1; final float alpha = getVisibleAlpha(viewCaptureNode, parentAlpha); Loading @@ -182,6 +207,8 @@ public class ViewCaptureAnalyzer { final float top = topShift + (viewCaptureNode.getTop() + viewCaptureNode.getTranslationY()) * parentScaleY + viewCaptureNode.getHeight() * (parentScaleY - scaleY) / 2; final float width = viewCaptureNode.getWidth() * scaleX; final float height = viewCaptureNode.getHeight() * scaleY; // Initialize new analysis node final AnalysisNode newAnalysisNode = new AnalysisNode(); Loading @@ -192,6 +219,8 @@ public class ViewCaptureAnalyzer { newAnalysisNode.parent = parent; newAnalysisNode.left = left; newAnalysisNode.top = top; newAnalysisNode.right = left + width; newAnalysisNode.bottom = top + height; newAnalysisNode.scaleX = scaleX; newAnalysisNode.scaleY = scaleY; newAnalysisNode.alpha = alpha; Loading @@ -216,11 +245,11 @@ public class ViewCaptureAnalyzer { } // Detect anomalies for the view. if (frameN != 0 && !viewCaptureNode.getWillNotDraw()) { if (!isFirstFrame && !viewCaptureNode.getWillNotDraw()) { Arrays.stream(ANOMALY_DETECTORS).forEach( detector -> detectAnomaly(detector, frameN, oldAnalysisNode, newAnalysisNode, anomalies, frameTimeNs) anomalies, frameTimeNs, windowSizePx) ); } lastSeenNodes.put(hashcode, newAnalysisNode); Loading @@ -235,17 +264,19 @@ public class ViewCaptureAnalyzer { // transparent. if (child.getClassnameIndex() == scrimClassIndex) break; analyzeView(frameTimeNs, child, newAnalysisNode, frameN, leftShiftForChildren, analyzeView(frameTimeNs, child, newAnalysisNode, frameN, isFirstFrame, leftShiftForChildren, topShiftForChildren, viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies); viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies, windowSizePx); } } private static void detectAnomaly(AnomalyDetector detector, int frameN, AnalysisNode oldAnalysisNode, AnalysisNode newAnalysisNode, Map<String, String> anomalies, long frameTimeNs) { Map<String, String> anomalies, long frameTimeNs, int windowSizePx) { final String maybeAnomaly = detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN, frameTimeNs); detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN, frameTimeNs, windowSizePx); if (maybeAnomaly != null) { AnalysisNode latestInfo = newAnalysisNode != null ? newAnalysisNode : oldAnalysisNode; final String viewDiagPath = diagPathFromRoot(latestInfo); Loading Loading
tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java +2 −1 Original line number Diff line number Diff line Loading @@ -180,7 +180,8 @@ final class AlphaJumpDetector extends AnomalyDetector { } @Override String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp) { String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp, int windowSizePx) { // If the view was previously seen, proceed with analysis only if it was present in the // view hierarchy in the previous frame. if (oldInfo != null && oldInfo.frameN != frameN) return null; Loading
tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java +9 −8 Original line number Diff line number Diff line Loading @@ -72,13 +72,14 @@ abstract class AnomalyDetector { * hierarchy before 'currentFrame'. 'null' means that the view is first seen * in the 'currentFrame'. * @param newInfo the view in the view hierarchy of the 'currentFrame'. 'null' means that * the view is not present in the 'currentFrame', but was present in the previous * frame. * the view is not present in the 'currentFrame', but was present in the * previous frame. * @param frameN number of the current frame. * @param windowSizePx maximum of the window width and height, in pixels. * @return Anomaly diagnostic message if an anomaly has been detected; null otherwise. */ abstract String detectAnomalies( @Nullable ViewCaptureAnalyzer.AnalysisNode oldInfo, @Nullable ViewCaptureAnalyzer.AnalysisNode newInfo, int frameN, long frameTimeNs); long frameTimeNs, int windowSizePx); }
tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java +1 −1 Original line number Diff line number Diff line Loading @@ -110,7 +110,7 @@ final class FlashDetector extends AnomalyDetector { @Override String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long frameTimeNs) { long frameTimeNs, int windowSizePx) { // Should we check when a view was visible for a short period, then its alpha became 0? // Then 'lastVisible' time should be the last one still visible? // Check only transitions of alpha between 0 and 1? Loading
tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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. */ package com.android.launcher3.util.viewcapture_analysis; import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode; import java.util.List; /** * Anomaly detector that triggers an error when a view position jumps. */ final class PositionJumpDetector extends AnomalyDetector { // Maximum allowed jump in "milliwindows", i.e. a 1/1000's of the maximum of the window // dimensions. private static final float JUMP_MIW = 250; private static final String[] BORDER_NAMES = {"left", "top", "right", "bottom"}; // Commonly used parts of the paths to ignore. private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|"; private static final String DRAG_LAYER = CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|"; private static final String RECENTS_DRAG_LAYER = CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|"; private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of( DRAG_LAYER + "SearchContainerView:id/apps_view", DRAG_LAYER + "AppWidgetResizeFrame", DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view", CONTENT + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id" + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content", DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container", DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container", DRAG_LAYER + "LauncherDragView", RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView", CONTENT + "LauncherRootView:id/launcher|FloatingIconView", DRAG_LAYER + "FloatingTaskView", DRAG_LAYER + "LauncherRecentsView:id/overview_panel" )); // Per-AnalysisNode data that's specific to this detector. private static class NodeData { public boolean ignoreJumps; // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is // ignored. // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no // children. public IgnoreNode ignoreNode; } private NodeData getNodeData(AnalysisNode info) { return (NodeData) info.detectorsData[detectorOrdinal]; } @Override void initializeNode(AnalysisNode info) { final NodeData nodeData = new NodeData(); info.detectorsData[detectorOrdinal] = nodeData; // If the parent view ignores jumps, its descendants will too. final boolean parentIgnoresJumps = info.parent != null && getNodeData( info.parent).ignoreJumps; if (parentIgnoresJumps) { nodeData.ignoreJumps = true; return; } // Parent view doesn't ignore jumps. // Initialize this AnalysisNode's ignore-node with the corresponding child of the // ignore-node of the parent, if present. final IgnoreNode parentIgnoreNode = info.parent != null ? getNodeData(info.parent).ignoreNode : IGNORED_NODES_ROOT; nodeData.ignoreNode = parentIgnoreNode != null ? parentIgnoreNode.children.get(info.nodeIdentity) : null; // AnalysisNode will be ignored if the corresponding ignore-node is a leaf. nodeData.ignoreJumps = nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty(); } @Override String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long frameTimeNs, int windowSizePx) { // If the view is not present in the current frame, there can't be a jump detected in the // current frame. if (newInfo == null) return null; // We only detect position jumps if the view was visible in the previous frame. if (oldInfo == null || frameN != oldInfo.frameN + 1) return null; final NodeData newNodeData = getNodeData(newInfo); if (newNodeData.ignoreJumps) return null; final float[] positionDiffs = { newInfo.left - oldInfo.left, newInfo.top - oldInfo.top, newInfo.right - oldInfo.right, newInfo.bottom - oldInfo.bottom }; for (int i = 0; i < 4; ++i) { final float positionDiffAbs = Math.abs(positionDiffs[i]); if (positionDiffAbs * 1000 > JUMP_MIW * windowSizePx) { newNodeData.ignoreJumps = true; return String.format("Position jump: %s jumped by %s", BORDER_NAMES[i], positionDiffAbs); } } return null; } }
tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java +47 −16 Original line number Diff line number Diff line Loading @@ -36,7 +36,8 @@ public class ViewCaptureAnalyzer { // All detectors. They will be invoked in the order listed here. private static final AnomalyDetector[] ANOMALY_DETECTORS = { new AlphaJumpDetector(), new FlashDetector() new FlashDetector(), new PositionJumpDetector() }; static { Loading @@ -52,6 +53,8 @@ public class ViewCaptureAnalyzer { // Window coordinates of the view. public float left; public float top; public float right; public float bottom; // Visible scale and alpha, build recursively from the ancestor list. public float scaleX; Loading Loading @@ -81,7 +84,8 @@ public class ViewCaptureAnalyzer { @Override public String toString() { return String.format("view window coordinates: (%s, %s)", left, top); return String.format("view window coordinates: (%s, %s, %s, %s)", left, top, right, bottom); } } Loading Loading @@ -112,15 +116,33 @@ public class ViewCaptureAnalyzer { // As we go though frames, if a view becomes invisible, it stays in the map. final Map<Integer, AnalysisNode> lastSeenNodes = new HashMap<>(); int windowWidthPx = -1; int windowHeightPx = -1; for (int frameN = 0; frameN < windowData.getFrameDataCount(); ++frameN) { analyzeFrame(frameN, windowData.getFrameData(frameN), viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies); final FrameData frame = windowData.getFrameData(frameN); final ViewNode rootNode = frame.getNode(); // If the rotation or window size has changed, reset the analyzer state. final boolean isFirstFrame = windowWidthPx != rootNode.getWidth() || windowHeightPx != rootNode.getHeight(); if (isFirstFrame) { windowWidthPx = rootNode.getWidth(); windowHeightPx = rootNode.getHeight(); lastSeenNodes.clear(); } final int windowSizePx = Math.max(rootNode.getWidth(), rootNode.getHeight()); analyzeFrame(frameN, isFirstFrame, frame, viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies, windowSizePx); } } private static void analyzeFrame(int frameN, FrameData frame, ExportedData viewCaptureData, private static void analyzeFrame(int frameN, boolean isFirstFrame, FrameData frame, ExportedData viewCaptureData, Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex, Map<String, String> anomalies) { Map<String, String> anomalies, int windowSizePx) { // Analyze the node tree starting from the root. long frameTimeNs = frame.getTimestamp(); analyzeView( Loading @@ -128,12 +150,14 @@ public class ViewCaptureAnalyzer { frame.getNode(), /* parent = */ null, frameN, isFirstFrame, /* leftShift = */ 0, /* topShift = */ 0, viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies); anomalies, windowSizePx); // Analyze transitions when a view visible in the previous frame became invisible in the // current one. Loading @@ -148,7 +172,8 @@ public class ViewCaptureAnalyzer { /* oldInfo = */ info, /* newInfo = */ null, anomalies, frameTimeNs) frameTimeNs, windowSizePx) ); } info.timeBecameInvisibleNs = info.alpha == 1 ? frameTimeNs : -1; Loading @@ -159,9 +184,9 @@ public class ViewCaptureAnalyzer { private static void analyzeView(long frameTimeNs, ViewNode viewCaptureNode, AnalysisNode parent, int frameN, float leftShift, float topShift, ExportedData viewCaptureData, boolean isFirstFrame, float leftShift, float topShift, ExportedData viewCaptureData, Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex, Map<String, String> anomalies) { Map<String, String> anomalies, int windowSizePx) { // Skip analysis of invisible views final float parentAlpha = parent != null ? parent.alpha : 1; final float alpha = getVisibleAlpha(viewCaptureNode, parentAlpha); Loading @@ -182,6 +207,8 @@ public class ViewCaptureAnalyzer { final float top = topShift + (viewCaptureNode.getTop() + viewCaptureNode.getTranslationY()) * parentScaleY + viewCaptureNode.getHeight() * (parentScaleY - scaleY) / 2; final float width = viewCaptureNode.getWidth() * scaleX; final float height = viewCaptureNode.getHeight() * scaleY; // Initialize new analysis node final AnalysisNode newAnalysisNode = new AnalysisNode(); Loading @@ -192,6 +219,8 @@ public class ViewCaptureAnalyzer { newAnalysisNode.parent = parent; newAnalysisNode.left = left; newAnalysisNode.top = top; newAnalysisNode.right = left + width; newAnalysisNode.bottom = top + height; newAnalysisNode.scaleX = scaleX; newAnalysisNode.scaleY = scaleY; newAnalysisNode.alpha = alpha; Loading @@ -216,11 +245,11 @@ public class ViewCaptureAnalyzer { } // Detect anomalies for the view. if (frameN != 0 && !viewCaptureNode.getWillNotDraw()) { if (!isFirstFrame && !viewCaptureNode.getWillNotDraw()) { Arrays.stream(ANOMALY_DETECTORS).forEach( detector -> detectAnomaly(detector, frameN, oldAnalysisNode, newAnalysisNode, anomalies, frameTimeNs) anomalies, frameTimeNs, windowSizePx) ); } lastSeenNodes.put(hashcode, newAnalysisNode); Loading @@ -235,17 +264,19 @@ public class ViewCaptureAnalyzer { // transparent. if (child.getClassnameIndex() == scrimClassIndex) break; analyzeView(frameTimeNs, child, newAnalysisNode, frameN, leftShiftForChildren, analyzeView(frameTimeNs, child, newAnalysisNode, frameN, isFirstFrame, leftShiftForChildren, topShiftForChildren, viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies); viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies, windowSizePx); } } private static void detectAnomaly(AnomalyDetector detector, int frameN, AnalysisNode oldAnalysisNode, AnalysisNode newAnalysisNode, Map<String, String> anomalies, long frameTimeNs) { Map<String, String> anomalies, long frameTimeNs, int windowSizePx) { final String maybeAnomaly = detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN, frameTimeNs); detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN, frameTimeNs, windowSizePx); if (maybeAnomaly != null) { AnalysisNode latestInfo = newAnalysisNode != null ? newAnalysisNode : oldAnalysisNode; final String viewDiagPath = diagPathFromRoot(latestInfo); Loading