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 9658eaf64..9f721badd 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 @@ -427,49 +427,61 @@ public class InspectorSonarPlugin implements SonarPlugin { } } - void hitTest(final int touchX, final int touchY) throws Exception { + private Touch createTouch(final int touchX, final int touchY, final boolean ax) throws Exception { final SonarArray.Builder path = new SonarArray.Builder(); path.put(trackObject(mApplication)); - final Touch touch = - new Touch() { - int x = touchX; - int y = touchY; - Object node = mApplication; + return new Touch() { + int x = touchX; + int y = touchY; + Object node = mApplication; - @Override - public void finish() { - mConnection.send("select", new SonarObject.Builder().put("path", path).build()); - } - @Override - public void continueWithOffset( + @Override + public void finish() { + mConnection.send(ax ? "selectAX" : "select", new SonarObject.Builder().put("path", path).build()); + } + + @Override + public void continueWithOffset( final int childIndex, final int offsetX, final int offsetY) { - final Touch touch = this; - - new ErrorReportingRunnable(mConnection) { - @Override - protected void runOrThrow() throws Exception { - x -= offsetX; - y -= offsetY; - - node = assertNotNull(descriptorForObject(node).getChildAt(node, childIndex)); - path.put(trackObject(node)); - - final NodeDescriptor descriptor = descriptorForObject(node); - descriptor.hitTest(node, touch); - } - }.run(); - } + final Touch touch = this; + new ErrorReportingRunnable(mConnection) { @Override - public boolean containedIn(int l, int t, int r, int b) { - return x >= l && x <= r && y >= t && y <= b; - } - }; + protected void runOrThrow() throws Exception { + x -= offsetX; + y -= offsetY; + if (ax) { + node = assertNotNull(descriptorForObject(node).getAXChildAt(node, childIndex)); + } else { + node = assertNotNull(descriptorForObject(node).getChildAt(node, childIndex)); + } + + path.put(trackObject(node)); + final NodeDescriptor descriptor = descriptorForObject(node); + + if (ax) { + descriptor.axHitTest(node, touch); + } else { + descriptor.hitTest(node, touch); + } + } + }.run(); + } + + @Override + public boolean containedIn(int l, int t, int r, int b) { + return x >= l && x <= r && y >= t && y <= b; + } + }; + } + + void hitTest(final int touchX, final int touchY) throws Exception { final NodeDescriptor descriptor = descriptorForObject(mApplication); - descriptor.hitTest(mApplication, touch); + descriptor.hitTest(mApplication, createTouch(touchX, touchY, false)); + descriptor.axHitTest(mApplication, createTouch(touchX, touchY, true)); } private void setHighlighted(final String id, final boolean highlighted, final boolean isAlignmentMode) throws Exception { diff --git a/android/src/main/java/com/facebook/sonar/plugins/inspector/NodeDescriptor.java b/android/src/main/java/com/facebook/sonar/plugins/inspector/NodeDescriptor.java index 6fdab336d..4100df952 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/inspector/NodeDescriptor.java +++ b/android/src/main/java/com/facebook/sonar/plugins/inspector/NodeDescriptor.java @@ -177,6 +177,15 @@ public abstract class NodeDescriptor { */ public abstract void hitTest(T node, Touch touch) throws Exception; + /** + * Perform hit testing on the given ax node. Either continue the search in an ax child with {@link + * Touch#continueWithOffset(int, int, int, boolean)} or finish the hit testing on this ax node with {@link + * Touch#finish()} + */ + public void axHitTest(T node, Touch touch) throws Exception { + touch.finish(); + } + /** * @return A string indicating how this element should be decorated. Check with the Sonar desktop * app to see what values are supported. 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 feca8219b..5e34d4ff4 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 @@ -210,12 +210,11 @@ public class ApplicationDescriptor extends NodeDescriptor { } } - @Override - public void hitTest(ApplicationWrapper node, Touch touch) { + private void runHitTest(ApplicationWrapper node, Touch touch, boolean ax) throws Exception { final int childCount = getChildCount(node); for (int i = childCount - 1; i >= 0; i--) { - final Object child = getChildAt(node, i); + final Object child = ax ? getAXChildAt(node, i) : getChildAt(node, i); if (child instanceof Activity || child instanceof ViewGroup) { touch.continueWithOffset(i, 0, 0); return; @@ -225,6 +224,16 @@ public class ApplicationDescriptor extends NodeDescriptor { touch.finish(); } + @Override + public void hitTest(ApplicationWrapper node, Touch touch) throws Exception{ + runHitTest(node, touch, false); + } + + @Override + public void axHitTest(ApplicationWrapper node, Touch touch) throws Exception { + runHitTest(node, touch, true); + } + @Override public @Nullable String getDecoration(ApplicationWrapper obj) { return null; diff --git a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/TextViewDescriptor.java b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/TextViewDescriptor.java index 70ce60ed9..aecf9af9a 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/TextViewDescriptor.java +++ b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/TextViewDescriptor.java @@ -149,6 +149,12 @@ public class TextViewDescriptor extends NodeDescriptor { descriptor.hitTest(node, touch); } + @Override + public void axHitTest(TextView node, Touch touch) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(View.class); + descriptor.axHitTest(node, touch); + } + @Override public @Nullable String getDecoration(TextView node) throws Exception { final NodeDescriptor descriptor = descriptorForClass(View.class); 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 67ce04b37..098a61fc3 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 @@ -534,6 +534,11 @@ public class ViewDescriptor extends NodeDescriptor { touch.finish(); } + @Override + public void axHitTest(View node, Touch touch) { + touch.finish(); + } + @Override public @Nullable String getDecoration(View obj) { return null; diff --git a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ViewGroupDescriptor.java b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ViewGroupDescriptor.java index 320a03996..c1c09c83c 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ViewGroupDescriptor.java +++ b/android/src/main/java/com/facebook/sonar/plugins/inspector/descriptors/ViewGroupDescriptor.java @@ -248,13 +248,12 @@ public class ViewGroupDescriptor extends NodeDescriptor { descriptor.setHighlighted(node, selected, isAlignmentMode); } - @Override - public void hitTest(ViewGroup node, Touch touch) { + private void runHitTest(ViewGroup node, Touch touch) { for (int i = node.getChildCount() - 1; i >= 0; i--) { final View child = node.getChildAt(i); if (child instanceof HiddenNode - || child.getVisibility() != View.VISIBLE - || shouldSkip(child)) { + || child.getVisibility() != View.VISIBLE + || shouldSkip(child)) { continue; } @@ -277,6 +276,16 @@ public class ViewGroupDescriptor extends NodeDescriptor { touch.finish(); } + @Override + public void hitTest(ViewGroup node, Touch touch) { + runHitTest(node, touch); + } + + @Override + public void axHitTest(ViewGroup node, Touch touch) { + runHitTest(node, touch); + } + private static boolean shouldSkip(View view) { Object tag = view.getTag(R.id.sonar_skip_view_traversal); if (!(tag instanceof Boolean)) { diff --git a/android/src/main/java/com/facebook/sonar/plugins/litho/LithoViewDescriptor.java b/android/src/main/java/com/facebook/sonar/plugins/litho/LithoViewDescriptor.java index c19f9f23e..9f0b00995 100644 --- a/android/src/main/java/com/facebook/sonar/plugins/litho/LithoViewDescriptor.java +++ b/android/src/main/java/com/facebook/sonar/plugins/litho/LithoViewDescriptor.java @@ -135,6 +135,12 @@ public class LithoViewDescriptor extends NodeDescriptor { touch.continueWithOffset(0, 0, 0); } + @Override + public void axHitTest(LithoView node, Touch touch) throws Exception { + final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class); + descriptor.axHitTest(node, touch); + } + @Override public String getDecoration(LithoView node) throws Exception { final NodeDescriptor descriptor = descriptorForClass(ViewGroup.class); diff --git a/src/plugins/layout/index.js b/src/plugins/layout/index.js index 4bb1dd3a1..882ebb6e5 100644 --- a/src/plugins/layout/index.js +++ b/src/plugins/layout/index.js @@ -437,6 +437,48 @@ export default class Layout extends SonarPlugin { return AXToggleButtonEnabled && this.realClient.query.os === 'Android'; } + // expand tree and highlight click-to-inspect node that was found + onSelectResultsRecieved(path: Array, ax: boolean) { + this.getNodesAndDirectChildren(path, ax).then( + (elements: Array) => { + const selected = path[path.length - 1]; + + this.dispatchAction({ + elements, + type: ax ? 'UpdateAXElements' : 'UpdateElements', + }); + + // select node from ax tree if in ax mode + // select node from main tree if not in ax mode + // (also selects corresponding node in other tree if it exists) + if ((ax && this.state.inAXMode) || (!ax && !this.state.inAXMode)) { + const {key, AXkey} = this.getKeysFromSelected(selected); + this.dispatchAction({key, AXkey, type: 'SelectElement'}); + } + + this.dispatchAction({ + isSearchActive: false, + type: 'SetSearchActive', + }); + + for (const key of path) { + this.dispatchAction({ + expand: true, + key, + type: ax ? 'ExpandAXElement' : 'ExpandElement', + }); + } + + this.client.send('setHighlighted', { + id: selected, + isAlignmentMode: this.state.isAlignmentMode, + }); + + this.client.send('setSearchActive', {active: false}); + }, + ); + } + initAX() { this.client.call('getAXRoot').then((element: Element) => { this.dispatchAction({elements: [element], type: 'UpdateAXElements'}); @@ -482,6 +524,10 @@ export default class Layout extends SonarPlugin { ); }, ); + + this.client.subscribe('selectAX', ({path}: {path: Array}) => { + this.onSelectResultsRecieved(path, true); + }); } init() { @@ -513,26 +559,7 @@ export default class Layout extends SonarPlugin { ); this.client.subscribe('select', ({path}: {path: Array}) => { - this.getNodesAndDirectChildren(path, false).then( - (elements: Array) => { - const selected = path[path.length - 1]; - const {key, AXkey} = this.getKeysFromSelected(selected); - - this.dispatchAction({elements, type: 'UpdateElements'}); - this.dispatchAction({key, AXkey, type: 'SelectElement'}); - this.dispatchAction({isSearchActive: false, type: 'SetSearchActive'}); - - for (const key of path) { - this.dispatchAction({expand: true, key, type: 'ExpandElement'}); - } - - this.client.send('setHighlighted', { - id: selected, - isAlignmentMode: this.state.isAlignmentMode, - }); - this.client.send('setSearchActive', {active: false}); - }, - ); + this.onSelectResultsRecieved(path, false); }); if (this.axEnabled()) { @@ -568,7 +595,7 @@ export default class Layout extends SonarPlugin { getNodesAndDirectChildren( ids: Array, - ax: boolean, // always false at the moment bc only used for select + ax: boolean, ): Promise> { return this.getNodes(ids, {force: false, ax}).then( (elements: Array) => { @@ -698,13 +725,22 @@ export default class Layout extends SonarPlugin { }; onElementExpanded = (key: ElementID, deep: boolean) => { - if (deep) { - this.deepExpandElement(key, false); - this.deepExpandElement(key, true); - } else { - this.expandElement(key, false); - this.expandElement(key, true); + if (this.state.elements[key]) { + if (deep) { + this.deepExpandElement(key, false); + } else { + this.expandElement(key, false); + } } + + if (this.state.AXelements[key]) { + if (deep) { + this.deepExpandElement(key, true); + } else { + this.expandElement(key, true); + } + } + this.props.logger.track('usage', 'layout:element-expanded', { id: key, deep: deep,