Highlight the current talkback-focused element in the accessibility tree

Summary: Highlights the element corresponding to the view talkback is focused on in green in the ax tree (and updates live as talkback moves).

Reviewed By: blavalla

Differential Revision: D9021542

fbshipit-source-id: c3bf6f5625aacb0cd054032b33a50541b88b2eaf
This commit is contained in:
Sara Valderrama
2018-07-27 16:16:54 -07:00
committed by Facebook Github Bot
parent 6939292209
commit 33e6538477
10 changed files with 154 additions and 18 deletions

View File

@@ -10,6 +10,8 @@ package com.facebook.sonar.plugins.inspector;
import android.app.Application;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.view.accessibility.AccessibilityEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -170,20 +172,41 @@ public class InspectorSonarPlugin implements SonarPlugin {
@Override
public void onReceiveOnMainThread(SonarObject params, SonarResponder responder)
throws Exception {
List<View> viewRoots = mApplication.getViewRoots();
// for now only works if one view root
if (viewRoots.size() != 1) {
responder.error(
new SonarObject.Builder().put("message", "Too many view roots.").build());
return;
final List<View> viewRoots = mApplication.getViewRoots();
ViewGroup root = null;
for (int i = viewRoots.size() - 1; i >= 0; i--) {
if (viewRoots.get(i) instanceof ViewGroup) {
root = (ViewGroup) viewRoots.get(i);
break;
}
}
SonarObject response = getAXNode(trackObject(viewRoots.get(0)));
if (response == null) {
responder.error(
new SonarObject.Builder().put("message", "AX root node returned null.").build());
return;
if (root != null) {
// unlikely, but check to make sure accessibility functionality doesn't change
if (!ViewCompat.hasAccessibilityDelegate(root)) {
// add delegate to root to catch accessibility events so we can update focus in sonar
root.setAccessibilityDelegate(new View.AccessibilityDelegate() {
@Override
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, AccessibilityEvent event) {
int eventType = event.getEventType();
if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED || eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED) {
mConnection.send("axFocusEvent",
new SonarObject.Builder()
.put("isFocus", eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED)
.build());
}
return super.onRequestSendAccessibilityEvent(host, child, event);
}
});
}
responder.success(getAXNode(trackObject(root)));
}
responder.success(response);
}
};
@@ -550,6 +573,7 @@ public class InspectorSonarPlugin implements SonarPlugin {
.put("data", data)
.put("children", children)
.put("attributes", attributes)
.put("extraInfo", descriptor.getExtraInfo(obj))
.build();
}

View File

@@ -131,6 +131,12 @@ public class TextViewDescriptor extends NodeDescriptor<TextView> {
return descriptor.getAXAttributes(node);
}
@Override
public SonarObject getExtraInfo(TextView node) {
final NodeDescriptor descriptor = descriptorForClass(View.class);
return descriptor.getExtraInfo(node);
}
@Override
public void setHighlighted(TextView node, boolean selected) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(View.class);

View File

@@ -444,6 +444,7 @@ public class ViewDescriptor extends NodeDescriptor<View> {
return attributes;
}
@Override
public List<Named<String>> getAXAttributes(View node) throws Exception {
List<Named<String>> attributes = new ArrayList<>();
String role = AccessibilityRoleUtil.getRole(node).toString();
@@ -453,6 +454,11 @@ public class ViewDescriptor extends NodeDescriptor<View> {
return attributes;
}
@Override
public SonarObject getExtraInfo(View node) {
return new SonarObject.Builder().put("focused", AccessibilityUtil.isAXFocused(node)).build();
}
@Nullable
private static String getResourceId(View node) {
final int id = node.getId();

View File

@@ -235,6 +235,12 @@ public class ViewGroupDescriptor extends NodeDescriptor<ViewGroup> {
return descriptor.getAXAttributes(node);
}
@Override
public SonarObject getExtraInfo(ViewGroup node) {
final NodeDescriptor descriptor = descriptorForClass(View.class);
return descriptor.getExtraInfo(node);
}
@Override
public void setHighlighted(ViewGroup node, boolean selected) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(View.class);

View File

@@ -420,6 +420,18 @@ public final class AccessibilityUtil {
return nodeInfoProps.build();
}
public static boolean isAXFocused(View view) {
final AccessibilityNodeInfoCompat nodeInfo =
ViewAccessibilityHelper.createNodeInfoFromView(view);
if (nodeInfo == null) {
return false;
} else {
boolean focused = nodeInfo.isAccessibilityFocused();
nodeInfo.recycle();
return focused;
}
}
/**
* Modifies a {@link SonarObject.Builder} to add Talkback-specific Accessibiltiy properties to be
* shown in the Sonar Layout Inspector.

View File

@@ -117,6 +117,12 @@ public class LithoViewDescriptor extends NodeDescriptor<LithoView> {
return descriptor.getAXAttributes(node);
}
@Override
public SonarObject getExtraInfo(LithoView node) {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);
return descriptor.getExtraInfo(node);
}
@Override
public void setHighlighted(LithoView node, boolean selected) throws Exception {
final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class);