From d871ce0a0439ee409449463586c144e42a9c8a75 Mon Sep 17 00:00:00 2001 From: Paco Estevez Garcia Date: Wed, 1 Jul 2020 11:03:58 -0700 Subject: [PATCH] Extract TouchOverlayView to new class Summary: Before this diff, `TouchOverlayView` would be an inner non-static class,so it'd be difficult to track memory ownership for it. It also made `InspectorFlipperPlugin` longer and harder to read. Reviewed By: cekkaewnumchai Differential Revision: D22285744 fbshipit-source-id: 6fdd8c33a07be6ab900ebb28a8c3ebf3761fb598 --- .../inspector/InspectorFlipperPlugin.java | 195 ++--------------- .../plugins/inspector/TouchOverlayView.java | 202 ++++++++++++++++++ .../inspector/InspectorFlipperPluginTest.java | 17 +- 3 files changed, 232 insertions(+), 182 deletions(-) create mode 100644 android/src/main/java/com/facebook/flipper/plugins/inspector/TouchOverlayView.java diff --git a/android/src/main/java/com/facebook/flipper/plugins/inspector/InspectorFlipperPlugin.java b/android/src/main/java/com/facebook/flipper/plugins/inspector/InspectorFlipperPlugin.java index 62253f727..d1925a631 100644 --- a/android/src/main/java/com/facebook/flipper/plugins/inspector/InspectorFlipperPlugin.java +++ b/android/src/main/java/com/facebook/flipper/plugins/inspector/InspectorFlipperPlugin.java @@ -9,8 +9,6 @@ package com.facebook.flipper.plugins.inspector; import android.app.Application; import android.content.Context; -import android.util.Pair; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; @@ -24,11 +22,8 @@ import com.facebook.flipper.core.FlipperReceiver; import com.facebook.flipper.core.FlipperResponder; import com.facebook.flipper.plugins.common.MainThreadFlipperReceiver; import com.facebook.flipper.plugins.inspector.descriptors.ApplicationDescriptor; -import com.facebook.flipper.plugins.inspector.descriptors.utils.AccessibilityUtil; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import java.util.Stack; import javax.annotation.Nullable; public class InspectorFlipperPlugin implements FlipperPlugin { @@ -37,7 +32,7 @@ public class InspectorFlipperPlugin implements FlipperPlugin { private DescriptorMapping mDescriptorMapping; private ObjectTracker mObjectTracker; private String mHighlightedId; - private TouchOverlayView mTouchOverlay; + TouchOverlayView mTouchOverlay; private FlipperConnection mConnection; private @Nullable List mExtensionCommands; private boolean mShowLithoAccessibilitySettings; @@ -373,7 +368,18 @@ public class InspectorFlipperPlugin implements FlipperPlugin { if (root != null) { if (active) { - mTouchOverlay = new TouchOverlayView(root.getContext()); + mTouchOverlay = + new TouchOverlayView(root.getContext(), mConnection, mApplication) { + @Override + protected String trackObject(Object obj) throws Exception { + return InspectorFlipperPlugin.this.trackObject(obj); + } + + @Override + protected NodeDescriptor descriptorForObject(Object obj) { + return InspectorFlipperPlugin.this.descriptorForObject(obj); + } + }; root.addView(mTouchOverlay); root.bringChildToFront(mTouchOverlay); } else { @@ -413,175 +419,6 @@ public class InspectorFlipperPlugin implements FlipperPlugin { } }; - class TouchOverlayView extends View implements HiddenNode { - public TouchOverlayView(Context context) { - super(context); - setBackgroundColor(BoundsDrawable.COLOR_HIGHLIGHT_CONTENT); - } - - @Override - public boolean onHoverEvent(MotionEvent event) { - - // if in layout inspector and talkback is running, override the first click to locate the - // clicked view - if (mConnection != null - && AccessibilityUtil.isTalkbackEnabled(getContext()) - && event.getPointerCount() == 1) { - FlipperObject params = - new FlipperObject.Builder() - .put("type", "usage") - .put("eventName", "accessibility:clickToInspectTalkbackRunning") - .build(); - mConnection.send("track", params); - - final int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_HOVER_ENTER: - { - event.setAction(MotionEvent.ACTION_DOWN); - } - break; - case MotionEvent.ACTION_HOVER_MOVE: - { - event.setAction(MotionEvent.ACTION_MOVE); - } - break; - case MotionEvent.ACTION_HOVER_EXIT: - { - event.setAction(MotionEvent.ACTION_UP); - } - break; - } - return onTouchEvent(event); - } - - // otherwise use the default - return super.onHoverEvent(event); - } - - @Override - public boolean onTouchEvent(final MotionEvent event) { - if (event.getAction() != MotionEvent.ACTION_UP) { - return true; - } - - new ErrorReportingRunnable(mConnection) { - @Override - public void runOrThrow() throws Exception { - hitTest((int) event.getX(), (int) event.getY()); - } - }.run(); - - return true; - } - } - - private Pair> createTouch( - final int touchX, final int touchY, final boolean ax) throws Exception { - final Stack objStack = new Stack<>(); - objStack.push(new FlipperObject.Builder()); - - final Stack nodes = new Stack<>(); - nodes.push(mApplication); - - final Touch touch = - new Touch() { - int x = touchX; - int y = touchY; - - @Override - public void finish() {} - - @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 { - Object nextNode; - final Object currNode = nodes.peek(); - x -= offsetX; - y -= offsetY; - - if (ax) { - nextNode = - assertNotNull( - descriptorForObject(currNode).getAXChildAt(currNode, childIndex)); - } else { - nextNode = - assertNotNull(descriptorForObject(currNode).getChildAt(currNode, childIndex)); - } - - nodes.push(nextNode); - final String nodeID = trackObject(nextNode); - final NodeDescriptor descriptor = descriptorForObject(nextNode); - objStack.push(new FlipperObject.Builder()); - - if (ax) { - descriptor.axHitTest(nextNode, touch); - } else { - descriptor.hitTest(nextNode, touch); - } - - x += offsetX; - y += offsetY; - nodes.pop(); - final FlipperObject objTree = objStack.pop().build(); - objStack.peek().put(nodeID, objTree); - } - }.run(); - } - - @Override - public boolean containedIn(int l, int t, int r, int b) { - return x >= l && x <= r && y >= t && y <= b; - } - }; - - return new Pair<>(touch, objStack); - } - - // This is mainly for backward compatibility - private FlipperArray getPathFromTree(FlipperObject tree) { - final FlipperArray.Builder pathBuilder = new FlipperArray.Builder(); - FlipperObject subtree = tree; - Iterator it = subtree.keys(); - while (it.hasNext()) { - final String key = it.next(); - pathBuilder.put(key); - subtree = subtree.getObject(key); - it = subtree.keys(); - } - return pathBuilder.build(); - } - - void hitTest(final int touchX, final int touchY) throws Exception { - final NodeDescriptor descriptor = descriptorForObject(mApplication); - FlipperObject treeObj; - - Pair> pair = createTouch(touchX, touchY, false); - descriptor.hitTest(mApplication, pair.first); - treeObj = new FlipperObject.Builder().put(trackObject(mApplication), pair.second.pop()).build(); - mConnection.send( - "select", - new FlipperObject.Builder() - .put("tree", treeObj) - .put("path", getPathFromTree(treeObj)) - .build()); - - pair = createTouch(touchX, touchY, true); - descriptor.axHitTest(mApplication, pair.first); - treeObj = new FlipperObject.Builder().put(trackObject(mApplication), pair.second.pop()).build(); - mConnection.send( - "selectAX", - new FlipperObject.Builder() - .put("tree", treeObj) - .put("path", getPathFromTree(treeObj)) - .build()); - } - private void setHighlighted( final String id, final boolean highlighted, final boolean isAlignmentMode) throws Exception { final Object obj = mObjectTracker.get(id); @@ -745,7 +582,7 @@ public class InspectorFlipperPlugin implements FlipperPlugin { .build(); } - private String trackObject(Object obj) throws Exception { + String trackObject(Object obj) throws Exception { final NodeDescriptor descriptor = descriptorForObject(obj); final String id = descriptor.getId(obj); final Object curr = mObjectTracker.get(id); @@ -756,12 +593,12 @@ public class InspectorFlipperPlugin implements FlipperPlugin { return id; } - private NodeDescriptor descriptorForObject(Object obj) { + NodeDescriptor descriptorForObject(Object obj) { final Class c = assertNotNull(obj).getClass(); return (NodeDescriptor) mDescriptorMapping.descriptorForClass(c); } - private static Object assertNotNull(@Nullable Object o) { + static Object assertNotNull(@Nullable Object o) { if (o == null) { throw new RuntimeException("Unexpected null value"); } diff --git a/android/src/main/java/com/facebook/flipper/plugins/inspector/TouchOverlayView.java b/android/src/main/java/com/facebook/flipper/plugins/inspector/TouchOverlayView.java new file mode 100644 index 000000000..1a49e4d90 --- /dev/null +++ b/android/src/main/java/com/facebook/flipper/plugins/inspector/TouchOverlayView.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.flipper.plugins.inspector; + +import android.content.Context; +import android.util.Pair; +import android.view.MotionEvent; +import android.view.View; +import com.facebook.flipper.core.ErrorReportingRunnable; +import com.facebook.flipper.core.FlipperArray; +import com.facebook.flipper.core.FlipperConnection; +import com.facebook.flipper.core.FlipperObject; +import com.facebook.flipper.plugins.inspector.descriptors.utils.AccessibilityUtil; +import java.util.Iterator; +import java.util.Stack; + +abstract class TouchOverlayView extends View implements HiddenNode { + + private final FlipperConnection mConnection; + + private final ApplicationWrapper mApplication; + + public TouchOverlayView( + Context context, FlipperConnection connection, ApplicationWrapper wrapper) { + super(context); + mConnection = connection; + mApplication = wrapper; + setBackgroundColor(BoundsDrawable.COLOR_HIGHLIGHT_CONTENT); + } + + protected abstract String trackObject(Object obj) throws Exception; + + protected abstract NodeDescriptor descriptorForObject(Object obj); + + @Override + public boolean onHoverEvent(MotionEvent event) { + + // if in layout inspector and talkback is running, override the first click to locate the + // clicked view + if (mConnection != null + && AccessibilityUtil.isTalkbackEnabled(getContext()) + && event.getPointerCount() == 1) { + FlipperObject params = + new FlipperObject.Builder() + .put("type", "usage") + .put("eventName", "accessibility:clickToInspectTalkbackRunning") + .build(); + mConnection.send("track", params); + + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + { + event.setAction(MotionEvent.ACTION_DOWN); + } + break; + case MotionEvent.ACTION_HOVER_MOVE: + { + event.setAction(MotionEvent.ACTION_MOVE); + } + break; + case MotionEvent.ACTION_HOVER_EXIT: + { + event.setAction(MotionEvent.ACTION_UP); + } + break; + } + return onTouchEvent(event); + } + + // otherwise use the default + return super.onHoverEvent(event); + } + + @Override + public boolean onTouchEvent(final MotionEvent event) { + if (event.getAction() != MotionEvent.ACTION_UP) { + return true; + } + + new ErrorReportingRunnable(mConnection) { + @Override + public void runOrThrow() throws Exception { + hitTest((int) event.getX(), (int) event.getY()); + } + }.run(); + + return true; + } + + void hitTest(final int touchX, final int touchY) throws Exception { + final NodeDescriptor descriptor = descriptorForObject(mApplication); + FlipperObject treeObj; + + Pair> pair = createTouch(touchX, touchY, false); + descriptor.hitTest(mApplication, pair.first); + treeObj = new FlipperObject.Builder().put(trackObject(mApplication), pair.second.pop()).build(); + mConnection.send( + "select", + new FlipperObject.Builder() + .put("tree", treeObj) + .put("path", getPathFromTree(treeObj)) + .build()); + + pair = createTouch(touchX, touchY, true); + descriptor.axHitTest(mApplication, pair.first); + treeObj = new FlipperObject.Builder().put(trackObject(mApplication), pair.second.pop()).build(); + mConnection.send( + "selectAX", + new FlipperObject.Builder() + .put("tree", treeObj) + .put("path", getPathFromTree(treeObj)) + .build()); + } + + private Pair> createTouch( + final int touchX, final int touchY, final boolean ax) throws Exception { + final Stack objStack = new Stack<>(); + objStack.push(new FlipperObject.Builder()); + + final Stack nodes = new Stack<>(); + nodes.push(mApplication); + + final Touch touch = + new Touch() { + int x = touchX; + int y = touchY; + + @Override + public void finish() {} + + @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 { + Object nextNode; + final Object currNode = nodes.peek(); + x -= offsetX; + y -= offsetY; + + if (ax) { + nextNode = + InspectorFlipperPlugin.assertNotNull( + descriptorForObject(currNode).getAXChildAt(currNode, childIndex)); + } else { + nextNode = + InspectorFlipperPlugin.assertNotNull( + descriptorForObject(currNode).getChildAt(currNode, childIndex)); + } + + nodes.push(nextNode); + final String nodeID = trackObject(nextNode); + final NodeDescriptor descriptor = descriptorForObject(nextNode); + objStack.push(new FlipperObject.Builder()); + + if (ax) { + descriptor.axHitTest(nextNode, touch); + } else { + descriptor.hitTest(nextNode, touch); + } + + x += offsetX; + y += offsetY; + nodes.pop(); + final FlipperObject objTree = objStack.pop().build(); + objStack.peek().put(nodeID, objTree); + } + }.run(); + } + + @Override + public boolean containedIn(int l, int t, int r, int b) { + return x >= l && x <= r && y >= t && y <= b; + } + }; + + return new Pair<>(touch, objStack); + } + + // This is mainly for backward compatibility + private FlipperArray getPathFromTree(FlipperObject tree) { + final FlipperArray.Builder pathBuilder = new FlipperArray.Builder(); + FlipperObject subtree = tree; + Iterator it = subtree.keys(); + while (it.hasNext()) { + final String key = it.next(); + pathBuilder.put(key); + subtree = subtree.getObject(key); + it = subtree.keys(); + } + return pathBuilder.build(); + } +} diff --git a/android/src/test/java/com/facebook/flipper/plugins/inspector/InspectorFlipperPluginTest.java b/android/src/test/java/com/facebook/flipper/plugins/inspector/InspectorFlipperPluginTest.java index 1d2458be8..b2f527e75 100644 --- a/android/src/test/java/com/facebook/flipper/plugins/inspector/InspectorFlipperPluginTest.java +++ b/android/src/test/java/com/facebook/flipper/plugins/inspector/InspectorFlipperPluginTest.java @@ -22,7 +22,6 @@ import com.facebook.flipper.core.FlipperArray; import com.facebook.flipper.core.FlipperConnection; import com.facebook.flipper.core.FlipperDynamic; import com.facebook.flipper.core.FlipperObject; -import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin.TouchOverlayView; import com.facebook.flipper.plugins.inspector.descriptors.ApplicationDescriptor; import com.facebook.flipper.testing.FlipperConnectionMock; import com.facebook.flipper.testing.FlipperResponderMock; @@ -242,7 +241,6 @@ public class InspectorFlipperPluginTest { final InspectorFlipperPlugin plugin = new InspectorFlipperPlugin(mApp, mDescriptorMapping, null); final FlipperConnectionMock connection = new FlipperConnectionMock(); - plugin.onConnect(connection); final TestNode one = new TestNode(); one.id = "1"; @@ -263,7 +261,20 @@ public class InspectorFlipperPluginTest { root.children.add(three); mApplicationDescriptor.root = root; - plugin.hitTest(10, 10); + TouchOverlayView view = + new TouchOverlayView(mApp.getApplication(), connection, mApp) { + @Override + protected String trackObject(Object obj) throws Exception { + return plugin.trackObject(obj); + } + + @Override + protected NodeDescriptor descriptorForObject(Object obj) { + return plugin.descriptorForObject(obj); + } + }; + + view.hitTest(10, 10); assertThat( connection.sent.get("select"),