Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1dcfdc3b authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implementing detector of view position jumps" into udc-qpr-dev

parents 0ff8103c 9e1fbcbe
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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;
+9 −8
Original line number Diff line number Diff line
@@ -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);
}
+1 −1
Original line number Diff line number Diff line
@@ -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?
+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;
    }
}
+47 −16
Original line number Diff line number Diff line
@@ -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 {
@@ -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;
@@ -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);
        }
    }

@@ -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(
@@ -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.
@@ -148,7 +172,8 @@ public class ViewCaptureAnalyzer {
                                            /* oldInfo = */ info,
                                            /* newInfo = */ null,
                                            anomalies,
                                            frameTimeNs)
                                            frameTimeNs,
                                            windowSizePx)
                    );
                }
                info.timeBecameInvisibleNs = info.alpha == 1 ? frameTimeNs : -1;
@@ -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);
@@ -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();
@@ -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;
@@ -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);
@@ -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);