From 5ceb3e4ffe9be9118869dae7d52617042e3d9f6d Mon Sep 17 00:00:00 2001 From: Sara Valderrama Date: Mon, 16 Jul 2018 16:52:47 -0700 Subject: [PATCH] Include non-drawable litho components in AX tree + small sidebar edit Summary: Fixed issue with DebugComponentDescriptors being left out of accessibility tree so the AX tree now includes all Litho view nodes (not Litho accessibility nodes yet). Litho drawables have no accessibility properties so these are not included. Also changed default for getAXChildAt to do whatever the original view tree does for that node and added a getAXChildCount function to better customize the accessibility tree. Segmented the ax sidebar into properties directly form the view and properties derived from the AccessibilityNodeInfo. Differential Revision: D8861129 fbshipit-source-id: 987683ef45188aa9cb587cc0e5ffba8fbf40136d --- .../inspector/InspectorSonarPlugin.java | 2 +- android/plugins/inspector/NodeDescriptor.java | 7 ++- .../inspector/descriptors/ViewDescriptor.java | 22 ++++++---- .../descriptors/utils/AccessibilityUtil.java | 43 +++++++++++++++++++ .../litho-sonar/DebugComponentDescriptor.java | 31 ++++++++----- 5 files changed, 84 insertions(+), 21 deletions(-) diff --git a/android/plugins/inspector/InspectorSonarPlugin.java b/android/plugins/inspector/InspectorSonarPlugin.java index 1cc4d567e..a7f676e95 100644 --- a/android/plugins/inspector/InspectorSonarPlugin.java +++ b/android/plugins/inspector/InspectorSonarPlugin.java @@ -502,7 +502,7 @@ public class InspectorSonarPlugin implements SonarPlugin { new ErrorReportingRunnable(mConnection) { @Override protected void runOrThrow() throws Exception { - for (int i = 0, count = descriptor.getChildCount(obj); i < count; i++) { + for (int i = 0, count = descriptor.getAXChildCount(obj); i < count; i++) { final Object child = assertNotNull(descriptor.getAXChildAt(obj, i)); children.put(trackObject(child)); } diff --git a/android/plugins/inspector/NodeDescriptor.java b/android/plugins/inspector/NodeDescriptor.java index bb6adac89..c18ad462b 100644 --- a/android/plugins/inspector/NodeDescriptor.java +++ b/android/plugins/inspector/NodeDescriptor.java @@ -100,12 +100,17 @@ public abstract class NodeDescriptor { /** @return The number of children this node exposes in the inspector. */ public abstract int getChildCount(T node) throws Exception; + /** Gets child at index for AX tree. Ignores non-view children. */ + public int getAXChildCount(T node) throws Exception { + return getChildCount(node); + } + /** @return The child at index. */ public abstract Object getChildAt(T node, int index) throws Exception; /** Gets child at index for AX tree. Ignores non-view children. */ public @Nullable Object getAXChildAt(T node, int index) throws Exception { - return null; + return getChildAt(node, index); } /** diff --git a/android/plugins/inspector/descriptors/ViewDescriptor.java b/android/plugins/inspector/descriptors/ViewDescriptor.java index 80760b7ee..1b1041baa 100644 --- a/android/plugins/inspector/descriptors/ViewDescriptor.java +++ b/android/plugins/inspector/descriptors/ViewDescriptor.java @@ -36,6 +36,7 @@ import com.facebook.sonar.plugins.inspector.InspectorValue; import com.facebook.sonar.plugins.inspector.Named; import com.facebook.sonar.plugins.inspector.NodeDescriptor; import com.facebook.sonar.plugins.inspector.Touch; +import com.facebook.sonar.plugins.inspector.descriptors.utils.AccessibilityRoleUtil; import com.facebook.sonar.plugins.inspector.descriptors.utils.AccessibilityUtil; import com.facebook.sonar.plugins.inspector.descriptors.utils.EnumMapping; import com.facebook.sonar.plugins.inspector.descriptors.utils.ViewAccessibilityHelper; @@ -43,7 +44,6 @@ import com.facebook.stetho.common.android.ResourcesUtil; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import javax.annotation.Nullable; @@ -102,11 +102,6 @@ public class ViewDescriptor extends NodeDescriptor { return null; } - @Override - public @Nullable Object getAXChildAt(View node, int index) { - return null; - } - @Override public List> getData(View node) { final SonarObject.Builder viewProps = @@ -197,7 +192,9 @@ public class ViewDescriptor extends NodeDescriptor { @Override public List> getAXData(View node) { - return Arrays.asList(new Named<>("AX Properties", getAccessibilityData(node))); + return Arrays.asList( + new Named<>("Derived Props", AccessibilityUtil.getDerivedAXData(node)), + new Named<>("AX Props", AccessibilityUtil.getViewAXData(node))); } private static SonarObject getAccessibilityData(View view) { @@ -225,7 +222,9 @@ public class ViewDescriptor extends NodeDescriptor { @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void setValue(View node, String[] path, SonarDynamic value) { - if (path[0].equals("Accessibility")) { + if (path[0].equals("Accessibility") + || path[0].equals("AX Props") + || path[0].equals("Derived Props")) { setAccessibilityValue(node, path, value); } @@ -433,7 +432,12 @@ public class ViewDescriptor extends NodeDescriptor { } public List> getAXAttributes(View node) throws Exception { - return Collections.EMPTY_LIST; + List> attributes = new ArrayList<>(); + String role = AccessibilityRoleUtil.getRole(node).toString(); + if (!role.equals("NONE")) { + attributes.add(new Named<>("role", role)); + } + return attributes; } @Nullable diff --git a/android/plugins/inspector/descriptors/utils/AccessibilityUtil.java b/android/plugins/inspector/descriptors/utils/AccessibilityUtil.java index 3d556894d..608f0bc73 100644 --- a/android/plugins/inspector/descriptors/utils/AccessibilityUtil.java +++ b/android/plugins/inspector/descriptors/utils/AccessibilityUtil.java @@ -23,6 +23,7 @@ import android.view.accessibility.AccessibilityManager; import android.widget.EditText; import com.facebook.sonar.core.SonarArray; import com.facebook.sonar.core.SonarObject; +import com.facebook.sonar.plugins.inspector.InspectorValue; import javax.annotation.Nullable; /** @@ -354,4 +355,46 @@ public final class AccessibilityUtil { .put("talkback-description", getTalkbackDescription(view)); } } + + public static SonarObject getViewAXData(View view) { + final SonarObject.Builder props = new SonarObject.Builder(); + + // This needs to be an empty string to be mutable. See t20470623. + CharSequence contentDescription = + view.getContentDescription() != null ? view.getContentDescription() : ""; + props.put("content-description", InspectorValue.mutable(contentDescription)); + props.put("focusable", InspectorValue.mutable(view.isFocusable())); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + props.put( + "important-for-accessibility", + AccessibilityUtil.sImportantForAccessibilityMapping.get( + view.getImportantForAccessibility())); + } + + return props.build(); + } + + public static SonarObject getDerivedAXData(View view) { + final SonarObject.Builder props = new SonarObject.Builder(); + + if (!AccessibilityEvaluationUtil.isTalkbackFocusable(view)) { + String reason = getTalkbackIgnoredReasons(view); + props + .put("talkback-ignored", true) + .put("talkback-ignored-reasons", reason == null ? "" : reason); + } else { + String reason = getTalkbackFocusableReasons(view); + CharSequence description = getTalkbackDescription(view); + props + .put("talkback-focusable", true) + .put("talkback-focusable-reasons", reason == null ? "" : reason) + .put("talkback-description", description == null ? "" : description); + } + + SonarObject axProps = getAccessibilityNodeInfoProperties(view); + props.put("node-info", axProps == null ? "null" : axProps); + + return props.build(); + } } diff --git a/android/plugins/inspector/litho-sonar/DebugComponentDescriptor.java b/android/plugins/inspector/litho-sonar/DebugComponentDescriptor.java index f60e422aa..15d4e495f 100644 --- a/android/plugins/inspector/litho-sonar/DebugComponentDescriptor.java +++ b/android/plugins/inspector/litho-sonar/DebugComponentDescriptor.java @@ -12,7 +12,6 @@ import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.support.v4.util.Pair; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.view.View; import com.facebook.litho.Component; import com.facebook.litho.ComponentContext; @@ -32,7 +31,6 @@ import com.facebook.sonar.plugins.inspector.NodeDescriptor; import com.facebook.sonar.plugins.inspector.Touch; import com.facebook.sonar.plugins.inspector.descriptors.ObjectDescriptor; import com.facebook.sonar.plugins.inspector.descriptors.utils.AccessibilityUtil; -import com.facebook.sonar.plugins.inspector.descriptors.utils.ViewAccessibilityHelper; import com.facebook.yoga.YogaAlign; import com.facebook.yoga.YogaDirection; import com.facebook.yoga.YogaEdge; @@ -126,12 +124,12 @@ public class DebugComponentDescriptor extends NodeDescriptor { } @Override - public String getAXName(DebugComponent node) { - View v = node.getComponentHost(); - AccessibilityNodeInfoCompat nodeInfo = ViewAccessibilityHelper.createNodeInfoFromView(v); - String name = nodeInfo.getClassName().toString(); - nodeInfo.recycle(); - return name; + public String getAXName(DebugComponent node) throws Exception { + NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass()); + if (componentDescriptor.getClass() != ObjectDescriptor.class) { + return componentDescriptor.getAXName(node.getComponent()); + } + return node.getComponent().getSimpleName(); } @Override @@ -143,6 +141,15 @@ public class DebugComponentDescriptor extends NodeDescriptor { } } + @Override + public int getAXChildCount(DebugComponent node) { + if (node.getMountedView() != null) { + return 1; + } else { + return node.getChildComponents().size(); + } + } + @Override public Object getChildAt(DebugComponent node, int index) { final View mountedView = node.getMountedView(); @@ -158,7 +165,7 @@ public class DebugComponentDescriptor extends NodeDescriptor { } @Override - public @Nullable Object getAXChildAt(DebugComponent node, int index) { + public Object getAXChildAt(DebugComponent node, int index) { final View mountedView = node.getMountedView(); if (mountedView != null) { @@ -201,7 +208,11 @@ public class DebugComponentDescriptor extends NodeDescriptor { } @Override - public List> getAXData(DebugComponent node) { + public List> getAXData(DebugComponent node) throws Exception { + NodeDescriptor componentDescriptor = descriptorForClass(node.getComponent().getClass()); + if (componentDescriptor.getClass() != ObjectDescriptor.class) { + return componentDescriptor.getAXData(node.getComponent()); + } final List> data = new ArrayList<>(); final SonarObject accessibilityData = getAccessibilityData(node); if (accessibilityData != null) {