From 656044ce69b31901bc35dfba4e1452c4e5fce0d1 Mon Sep 17 00:00:00 2001 From: Sara Valderrama Date: Mon, 13 Aug 2018 13:21:48 -0700 Subject: [PATCH] AX tree expands with main tree on search (cannot search ax tree yet) Summary: AX tree will now stay in sync with the main tree when searching. Also allows user to completely erase search (previously had left one letter highlighted in the tree even if entire query was erased). Reviewed By: danielbuechele Differential Revision: D9276721 fbshipit-source-id: 5272bb9cf3400ad3eb9d16bf438b0e5d4b551c6a --- .../inspector/InspectorSonarPlugin.java | 16 +++++-- .../plugins/inspector/SearchResultNode.java | 5 +- .../descriptors/ApplicationDescriptor.java | 5 ++ .../inspector/descriptors/ViewDescriptor.java | 5 +- .../utils/AccessibilityRoleUtil.java | 3 ++ src/plugins/layout/index.js | 46 +++++++++++++------ 6 files changed, 60 insertions(+), 20 deletions(-) diff --git a/android/src/main/java/com/facebook/sonar/plugins/inspector/InspectorSonarPlugin.java b/android/src/main/java/com/facebook/sonar/plugins/inspector/InspectorSonarPlugin.java index bc22cd90a..a2e54b357 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/inspector/InspectorSonarPlugin.java +++ b/android/src/main/java/com/facebook/sonar/plugins/inspector/InspectorSonarPlugin.java @@ -372,7 +372,9 @@ public class InspectorSonarPlugin implements SonarPlugin { public void onReceiveOnMainThread(SonarObject params, SonarResponder responder) throws Exception { final String query = params.getString("query"); - final SearchResultNode matchTree = searchTree(query.toLowerCase(), mApplication); + final boolean axEnabled = params.getBoolean("axEnabled"); + + final SearchResultNode matchTree = searchTree(query.toLowerCase(), mApplication, axEnabled); final SonarObject results = matchTree == null ? null : matchTree.toSonarObject(); final SonarObject response = new SonarObject.Builder().put("results", results).put("query", query).build(); @@ -503,14 +505,19 @@ public class InspectorSonarPlugin implements SonarPlugin { descriptor.setHighlighted(obj, highlighted, isAlignmentMode); } - public SearchResultNode searchTree(String query, Object obj) throws Exception { + private boolean hasAXNode(SonarObject node) { + SonarObject extraInfo = node.getObject("extraInfo"); + return extraInfo != null && extraInfo.getBoolean("hasAXNode"); + } + + public SearchResultNode searchTree(String query, Object obj, boolean axEnabled) throws Exception { final NodeDescriptor descriptor = descriptorForObject(obj); List childTrees = null; boolean isMatch = descriptor.matches(query, obj); for (int i = 0; i < descriptor.getChildCount(obj); i++) { Object child = descriptor.getChildAt(obj, i); - SearchResultNode childNode = searchTree(query, child); + SearchResultNode childNode = searchTree(query, child, axEnabled); if (childNode != null) { if (childTrees == null) { childTrees = new ArrayList<>(); @@ -521,7 +528,8 @@ public class InspectorSonarPlugin implements SonarPlugin { if (isMatch || childTrees != null) { final String id = trackObject(obj); - return new SearchResultNode(id, isMatch, getNode(id), childTrees); + SonarObject node = getNode(id); + return new SearchResultNode(id, isMatch, node, childTrees, axEnabled && hasAXNode(node) ? getAXNode(id) : null); } return null; } diff --git a/android/src/main/java/com/facebook/sonar/plugins/inspector/SearchResultNode.java b/android/src/main/java/com/facebook/sonar/plugins/inspector/SearchResultNode.java index 2c6ef5ad4..28829cf21 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/inspector/SearchResultNode.java +++ b/android/src/main/java/com/facebook/sonar/plugins/inspector/SearchResultNode.java @@ -18,15 +18,17 @@ public class SearchResultNode { private final String id; private final boolean isMatch; private final SonarObject element; + private final SonarObject axElement; @Nullable private final List children; SearchResultNode( - String id, boolean isMatch, SonarObject element, @Nullable List children) { + String id, boolean isMatch, SonarObject element, List children, SonarObject axElement) { this.id = id; this.isMatch = isMatch; this.element = element; this.children = children; + this.axElement = axElement; } SonarObject toSonarObject() { @@ -44,6 +46,7 @@ public class SearchResultNode { return new SonarObject.Builder() .put("id", this.id) .put("isMatch", this.isMatch) + .put("axElement", this.axElement) .put("element", this.element) .put("children", childArray) .build(); diff --git a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ApplicationDescriptor.java b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ApplicationDescriptor.java index 4b713c72d..da1b7b14e 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ApplicationDescriptor.java +++ b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ApplicationDescriptor.java @@ -206,6 +206,11 @@ public class ApplicationDescriptor extends NodeDescriptor { return Collections.EMPTY_LIST; } + @Override + public SonarObject getExtraInfo(ApplicationWrapper node) { + return new SonarObject.Builder().put("hasAXNode", true).build(); + } + @Override public void setHighlighted(ApplicationWrapper node, boolean selected, boolean isAlignmentMode) throws Exception { final int childCount = getChildCount(node); diff --git a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ViewDescriptor.java b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ViewDescriptor.java index 098a61fc3..321fbed8c 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ViewDescriptor.java +++ b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ViewDescriptor.java @@ -463,7 +463,10 @@ public class ViewDescriptor extends NodeDescriptor { @Override public SonarObject getExtraInfo(View node) { - return new SonarObject.Builder().put("focused", AccessibilityUtil.isAXFocused(node)).build(); + return new SonarObject.Builder() + .put("focused", AccessibilityUtil.isAXFocused(node)) + .put("hasAXNode", true) + .build(); } @Nullable diff --git a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/utils/AccessibilityRoleUtil.java b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/utils/AccessibilityRoleUtil.java index 367b7732f..766616f72 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/utils/AccessibilityRoleUtil.java +++ b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/utils/AccessibilityRoleUtil.java @@ -86,6 +86,9 @@ public class AccessibilityRoleUtil { } public static AccessibilityRole getRole(View view) { + if (view == null) { + return AccessibilityRole.NONE; + } AccessibilityNodeInfoCompat nodeInfo = AccessibilityNodeInfoCompat.obtain(); ViewCompat.onInitializeAccessibilityNodeInfo(view, nodeInfo); AccessibilityRole role = getRole(nodeInfo); diff --git a/src/plugins/layout/index.js b/src/plugins/layout/index.js index 807f34ffb..aba54121d 100644 --- a/src/plugins/layout/index.js +++ b/src/plugins/layout/index.js @@ -107,6 +107,7 @@ type SearchResultTree = {| isMatch: Boolean, children: ?Array, element: Element, + axElement: Element, |}; const LoadingSpinner = LoadingIndicator.extends({ @@ -333,15 +334,17 @@ export default class Layout extends SonarPlugin { }; search(query: string) { - if (!query) { - return; - } this.setState({ outstandingSearchQuery: query, }); - this.client - .call('getSearchResults', {query: query}) - .then(response => this.displaySearchResults(response)); + + if (!query) { + this.displaySearchResults({query: '', results: null}); + } else { + this.client + .call('getSearchResults', {query: query, axEnabled: this.axEnabled()}) + .then(response => this.displaySearchResults(response)); + } } executeCommand(command: string) { @@ -391,7 +394,7 @@ export default class Layout extends SonarPlugin { results, query, }: { - results: SearchResultTree, + results: ?SearchResultTree, query: string, }) { const elements = this.getElementsFromSearchResultTree(results); @@ -405,10 +408,29 @@ export default class Layout extends SonarPlugin { elements: elements.map(x => x.element), type: 'UpdateElements', }); + this.dispatchAction({ elements: idsToExpand, type: 'ExpandElements', }); + + if (this.axEnabled()) { + const AXelements = elements.filter(x => x.axElement); + const AXidsToExpand = AXelements.filter(x => x.hasChildren).map( + x => x.axElement.id, + ); + + this.dispatchAction({ + elements: AXelements.map(x => x.axElement), + type: 'UpdateAXElements', + }); + + this.dispatchAction({ + elements: AXidsToExpand, + type: 'ExpandAXElements', + }); + } + this.setState({ searchResults: { matches: new Set( @@ -422,7 +444,7 @@ export default class Layout extends SonarPlugin { }); } - getElementsFromSearchResultTree(tree: SearchResultTree) { + getElementsFromSearchResultTree(tree: ?SearchResultTree) { if (!tree) { return []; } @@ -432,6 +454,7 @@ export default class Layout extends SonarPlugin { isMatch: tree.isMatch, hasChildren: Boolean(tree.children), element: tree.element, + axElement: tree.axElement, }, ]; if (tree.children) { @@ -842,12 +865,7 @@ export default class Layout extends SonarPlugin { onElementSelected = debounce((selectedKey: ElementID) => { const {key, AXkey} = this.getKeysFromSelected(selectedKey); - - this.dispatchAction({ - key: key, - AXkey: AXkey, - type: 'SelectElement', - }); + this.dispatchAction({key, AXkey, type: 'SelectElement'}); this.client.send('setHighlighted', { id: selectedKey,