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

Commit 163633a9 authored by Vadim Tryshev's avatar Vadim Tryshev Committed by Android (Google) Code Review
Browse files

Merge "Optimizing application of the ignore-list of nodes for...

Merge "Optimizing application of the ignore-list of nodes for AlphaJumpDetector. Instead of building the full path of each node, and then searching it in the PATHS_TO_IGNORE set, we are descending the tree of the nodes to ignore. This saves us building the whole path for each node." into udc-qpr-dev
parents 06cb9634 4824afe5
Loading
Loading
Loading
Loading
+73 −9
Original line number Diff line number Diff line
@@ -15,13 +15,12 @@
 */
package com.android.launcher3.util.viewcapture_analysis;

import static com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.diagPathFromRoot;

import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode;
import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnomalyDetector;

import java.util.Collection;
import java.util.Set;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Anomaly detector that triggers an error when alpha of a view changes too rapidly.
@@ -34,7 +33,7 @@ final class AlphaJumpDetector extends AnomalyDetector {
            CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|";

    // Paths of nodes that are excluded from analysis.
    private static final Collection<String> PATHS_TO_IGNORE = Set.of(
    private static final Iterable<String> PATHS_TO_IGNORE = List.of(
            CONTENT
                    + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
                    + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content"
@@ -126,15 +125,80 @@ final class AlphaJumpDetector extends AnomalyDetector {
                    + ":id/overview_actions_view|LinearLayout:id/action_buttons|Button:id"
                    + "/action_select"
    );

    /**
     * Element of the tree of ignored nodes.
     * If the "children" map is empty, then this node should be ignored, i.e. alpha jumps analysis
     * shouldn't run for it.
     * I.e. ignored nodes correspond to the leaves in the ignored nodes tree.
     */
    private static class IgnoreNode {
        // Map from child node identities to ignore-nodes for these children.
        public final Map<String, IgnoreNode> children = new HashMap<>();
    }

    private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree();

    // Converts the list of full paths of nodes to ignore to a more efficient tree of ignore-nodes.
    private static IgnoreNode buildIgnoreNodesTree() {
        final IgnoreNode root = new IgnoreNode();
        for (String pathToIgnore : PATHS_TO_IGNORE) {
            // Scan the diag path of an ignored node and add its elements into the tree.
            IgnoreNode currentIgnoreNode = root;
            for (String part : pathToIgnore.split("\\|")) {
                // Ensure that the child of the node is added to the tree.
                IgnoreNode child = currentIgnoreNode.children.get(part);
                if (child == null) {
                    currentIgnoreNode.children.put(part, child = new IgnoreNode());
                }
                currentIgnoreNode = child;
            }
        }
        return root;
    }

    // Minimal increase or decrease of view's alpha between frames that triggers the error.
    private static final float ALPHA_JUMP_THRESHOLD = 1f;

    // Per-AnalysisNode data that's specific to this detector.
    private static class NodeData {
        public boolean ignoreAlphaJumps;

        // 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 alpha jumps, its descendants will too.
        final boolean parentIgnoreAlphaJumps = info.parent != null && info.parent.ignoreAlphaJumps;
        info.ignoreAlphaJumps = parentIgnoreAlphaJumps
                || PATHS_TO_IGNORE.contains(diagPathFromRoot(info));
        final boolean parentIgnoresAlphaJumps = info.parent != null && getNodeData(
                info.parent).ignoreAlphaJumps;
        if (parentIgnoresAlphaJumps) {
            nodeData.ignoreAlphaJumps = true;
            return;
        }

        // Parent view doesn't ignore alpha 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.ignoreAlphaJumps =
                nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty();
    }

    @Override
@@ -144,7 +208,7 @@ final class AlphaJumpDetector extends AnomalyDetector {
        if (oldInfo != null && oldInfo.frameN != frameN) return;

        final AnalysisNode latestInfo = newInfo != null ? newInfo : oldInfo;
        if (latestInfo.ignoreAlphaJumps) return;
        if (getNodeData(latestInfo).ignoreAlphaJumps) return;

        final float oldAlpha = oldInfo != null ? oldInfo.alpha : 0;
        final float newAlpha = newInfo != null ? newInfo.alpha : 0;
+26 −12
Original line number Diff line number Diff line
@@ -40,6 +40,9 @@ public class ViewCaptureAnalyzer {
     * Detector of one kind of anomaly.
     */
    abstract static class AnomalyDetector {
        // Index of this detector in ViewCaptureAnalyzer.ANOMALY_DETECTORS
        public int detectorOrdinal;

        /**
         * Initializes fields of the node that are specific to the anomaly detected by this
         * detector.
@@ -64,9 +67,13 @@ public class ViewCaptureAnalyzer {
    }

    // All detectors. They will be invoked in the order listed here.
    private static final Iterable<AnomalyDetector> ANOMALY_DETECTORS = Arrays.asList(
    private static final AnomalyDetector[] ANOMALY_DETECTORS = {
            new AlphaJumpDetector()
    );
    };

    static {
        for (int i = 0; i < ANOMALY_DETECTORS.length; ++i) ANOMALY_DETECTORS[i].detectorOrdinal = i;
    }

    // A view from view capture data converted to a form that's convenient for detecting anomalies.
    static class AnalysisNode {
@@ -86,7 +93,11 @@ public class ViewCaptureAnalyzer {
        public int frameN;
        public ViewNode viewCaptureNode;

        public boolean ignoreAlphaJumps;
        // Class name + resource id
        public String nodeIdentity;

        // Collection of detector-specific data for this node.
        public final Object[] detectorsData = new Object[ANOMALY_DETECTORS.length];

        @Override
        public String toString() {
@@ -139,7 +150,7 @@ public class ViewCaptureAnalyzer {
        for (AnalysisNode info : lastSeenNodes.values()) {
            if (info.frameN == frameN - 1) {
                if (!info.viewCaptureNode.getWillNotDraw()) {
                    ANOMALY_DETECTORS.forEach(
                    Arrays.stream(ANOMALY_DETECTORS).forEach(
                            detector -> detector.detectAnomalies(
                                    /* oldInfo = */ info,
                                    /* newInfo = */ null,
@@ -177,6 +188,8 @@ public class ViewCaptureAnalyzer {
        final AnalysisNode newAnalysisNode = new AnalysisNode();
        newAnalysisNode.className = viewCaptureData.getClassname(classIndex);
        newAnalysisNode.resourceId = viewCaptureNode.getId();
        newAnalysisNode.nodeIdentity =
                getNodeIdentity(newAnalysisNode.className, newAnalysisNode.resourceId);
        newAnalysisNode.parent = parent;
        newAnalysisNode.left = left;
        newAnalysisNode.top = top;
@@ -185,12 +198,13 @@ public class ViewCaptureAnalyzer {
        newAnalysisNode.alpha = alpha;
        newAnalysisNode.frameN = frameN;
        newAnalysisNode.viewCaptureNode = viewCaptureNode;
        ANOMALY_DETECTORS.forEach(detector -> detector.initializeNode(newAnalysisNode));
        Arrays.stream(ANOMALY_DETECTORS).forEach(
                detector -> detector.initializeNode(newAnalysisNode));

        // Detect anomalies for the view
        final AnalysisNode oldAnalysisNode = lastSeenNodes.get(hashcode); // may be null
        if (frameN != 0 && !viewCaptureNode.getWillNotDraw()) {
            ANOMALY_DETECTORS.forEach(
            Arrays.stream(ANOMALY_DETECTORS).forEach(
                    detector -> detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN));
        }
        lastSeenNodes.put(hashcode, newAnalysisNode);
@@ -221,18 +235,18 @@ public class ViewCaptureAnalyzer {
        return className.substring(className.lastIndexOf(".") + 1);
    }

    static String diagPathFromRoot(AnalysisNode nodeBox) {
        final StringBuilder path = new StringBuilder(diagPathElement(nodeBox));
    private static String diagPathFromRoot(AnalysisNode nodeBox) {
        final StringBuilder path = new StringBuilder(nodeBox.nodeIdentity);
        for (AnalysisNode ancestor = nodeBox.parent; ancestor != null; ancestor = ancestor.parent) {
            path.insert(0, diagPathElement(ancestor) + "|");
            path.insert(0, ancestor.nodeIdentity + "|");
        }
        return path.toString();
    }

    private static String diagPathElement(AnalysisNode nodeBox) {
    private static String getNodeIdentity(String className, String resourceId) {
        final StringBuilder sb = new StringBuilder();
        sb.append(classNameToSimpleName(nodeBox.className));
        if (!"NO_ID".equals(nodeBox.resourceId)) sb.append(":" + nodeBox.resourceId);
        sb.append(classNameToSimpleName(className));
        if (!"NO_ID".equals(resourceId)) sb.append(":" + resourceId);
        return sb.toString();
    }
}