diff options
Diffstat (limited to 'core/java')
8 files changed, 487 insertions, 18 deletions
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 5f7a17d..27a03b6 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -54,6 +54,10 @@ interface IAccessibilityServiceConnection { int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId); + boolean computeClickPointInScreen(int accessibilityWindowId, long accessibilityNodeId, + int interactionId, IAccessibilityInteractionConnectionCallback callback, + long threadId); + AccessibilityWindowInfo getWindow(int windowId); List<AccessibilityWindowInfo> getWindows(); diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index a10dda3..a283b91 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -19,7 +19,6 @@ package android.view; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -101,7 +100,7 @@ final class AccessibilityInteractionController { IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); - message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID; + message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID; message.arg1 = flags; SomeArgs args = SomeArgs.obtain(); @@ -176,7 +175,7 @@ final class AccessibilityInteractionController { IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); - message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID; + message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID; message.arg1 = flags; message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); @@ -261,7 +260,7 @@ final class AccessibilityInteractionController { IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { Message message = mHandler.obtainMessage(); - message.what = PrivateHandler.MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT; + message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT; message.arg1 = flags; SomeArgs args = SomeArgs.obtain(); @@ -637,6 +636,95 @@ final class AccessibilityInteractionController { } } + public void computeClickPointInScreenClientThread(long accessibilityNodeId, + Region interactiveRegion, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, + long interrogatingTid, MagnificationSpec spec) { + Message message = mHandler.obtainMessage(); + message.what = PrivateHandler.MSG_COMPUTE_CLICK_POINT_IN_SCREEN; + + SomeArgs args = SomeArgs.obtain(); + args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId); + args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId); + args.argi3 = interactionId; + args.arg1 = callback; + args.arg2 = spec; + args.arg3 = interactiveRegion; + + message.obj = args; + + // If the interrogation is performed by the same thread as the main UI + // thread in this process, set the message as a static reference so + // after this call completes the same thread but in the interrogating + // client can handle the message to generate the result. + if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) { + AccessibilityInteractionClient.getInstanceForThread( + interrogatingTid).setSameThreadMessage(message); + } else { + mHandler.sendMessage(message); + } + } + + private void computeClickPointInScreenUiThread(Message message) { + SomeArgs args = (SomeArgs) message.obj; + final int accessibilityViewId = args.argi1; + final int virtualDescendantId = args.argi2; + final int interactionId = args.argi3; + final IAccessibilityInteractionConnectionCallback callback = + (IAccessibilityInteractionConnectionCallback) args.arg1; + final MagnificationSpec spec = (MagnificationSpec) args.arg2; + final Region interactiveRegion = (Region) args.arg3; + args.recycle(); + + boolean succeeded = false; + Point point = mTempPoint; + try { + if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) { + return; + } + View target = null; + if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + target = findViewByAccessibilityId(accessibilityViewId); + } else { + target = mViewRootImpl.mView; + } + if (target != null && isShown(target)) { + AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider(); + if (provider != null) { + // For virtual views just use the center of the bounds in screen. + AccessibilityNodeInfo node = null; + if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + node = provider.createAccessibilityNodeInfo(virtualDescendantId); + } else { + node = provider.createAccessibilityNodeInfo( + AccessibilityNodeProvider.HOST_VIEW_ID); + } + if (node != null) { + succeeded = true; + Rect boundsInScreen = mTempRect; + node.getBoundsInScreen(boundsInScreen); + point.set(boundsInScreen.centerX(), boundsInScreen.centerY()); + } + } else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { + // For a real view, ask the view to compute the click point. + succeeded = target.computeClickPointInScreenForAccessibility( + interactiveRegion, point); + } + } + } finally { + try { + Point result = null; + if (succeeded) { + applyAppScaleAndMagnificationSpecIfNeeded(point, spec); + result = point; + } + callback.setComputeClickPointInScreenActionResult(result, interactionId); + } catch (RemoteException re) { + /* ignore - the other side will time out */ + } + } + } + private View findViewByAccessibilityId(int accessibilityId) { View root = mViewRootImpl.mView; if (root == null) { @@ -688,6 +776,26 @@ final class AccessibilityInteractionController { } } + private void applyAppScaleAndMagnificationSpecIfNeeded(Point point, + MagnificationSpec spec) { + final float applicationScale = mViewRootImpl.mAttachInfo.mApplicationScale; + if (!shouldApplyAppScaleAndMagnificationSpec(applicationScale, spec)) { + return; + } + + if (applicationScale != 1.0f) { + point.x *= applicationScale; + point.y *= applicationScale; + } + + if (spec != null) { + point.x *= spec.scale; + point.y *= spec.scale; + point.x += (int) spec.offsetX; + point.y += (int) spec.offsetY; + } + } + private void applyAppScaleAndMagnificationSpecIfNeeded(AccessibilityNodeInfo info, MagnificationSpec spec) { if (info == null) { @@ -1080,11 +1188,12 @@ final class AccessibilityInteractionController { private class PrivateHandler extends Handler { private final static int MSG_PERFORM_ACCESSIBILITY_ACTION = 1; - private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; - private final static int MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID = 3; - private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 4; + private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2; + private final static int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3; + private final static int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4; private final static int MSG_FIND_FOCUS = 5; private final static int MSG_FOCUS_SEARCH = 6; + private final static int MSG_COMPUTE_CLICK_POINT_IN_SCREEN = 7; public PrivateHandler(Looper looper) { super(looper); @@ -1096,16 +1205,18 @@ final class AccessibilityInteractionController { switch (type) { case MSG_PERFORM_ACCESSIBILITY_ACTION: return "MSG_PERFORM_ACCESSIBILITY_ACTION"; - case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: - return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID"; - case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: - return "MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID"; - case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: - return "MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT"; + case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: + return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID"; + case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: + return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID"; + case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: + return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT"; case MSG_FIND_FOCUS: return "MSG_FIND_FOCUS"; case MSG_FOCUS_SEARCH: return "MSG_FOCUS_SEARCH"; + case MSG_COMPUTE_CLICK_POINT_IN_SCREEN: + return "MSG_COMPUTE_CLICK_POINT_IN_SCREEN"; default: throw new IllegalArgumentException("Unknown message type: " + type); } @@ -1115,16 +1226,16 @@ final class AccessibilityInteractionController { public void handleMessage(Message message) { final int type = message.what; switch (type) { - case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID: { + case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: { findAccessibilityNodeInfoByAccessibilityIdUiThread(message); } break; case MSG_PERFORM_ACCESSIBILITY_ACTION: { perfromAccessibilityActionUiThread(message); } break; - case MSG_FIND_ACCESSIBLITY_NODE_INFOS_BY_VIEW_ID: { + case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: { findAccessibilityNodeInfosByViewIdUiThread(message); } break; - case MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT: { + case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: { findAccessibilityNodeInfosByTextUiThread(message); } break; case MSG_FIND_FOCUS: { @@ -1133,6 +1244,9 @@ final class AccessibilityInteractionController { case MSG_FOCUS_SEARCH: { focusSearchUiThread(message); } break; + case MSG_COMPUTE_CLICK_POINT_IN_SCREEN: { + computeClickPointInScreenUiThread(message); + } break; default: throw new IllegalArgumentException("Unknown message type: " + type); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 770e78c..82c5425 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -35,6 +35,8 @@ import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Outline; import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PathMeasure; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.PorterDuff; @@ -5731,6 +5733,136 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Computes a point on which a sequence of a down/up event can be sent to + * trigger clicking this view. This method is for the exclusive use by the + * accessibility layer to determine where to send a click event in explore + * by touch mode. + * + * @param interactiveRegion The interactive portion of this window. + * @param outPoint The point to populate. + * @return True of such a point exists. + */ + boolean computeClickPointInScreenForAccessibility(Region interactiveRegion, + Point outPoint) { + // Since the interactive portion of the view is a region but as a view + // may have a transformation matrix which cannot be applied to a + // region we compute the view bounds rectangle and all interactive + // predecessor's and sibling's (siblings of predecessors included) + // rectangles that intersect the view bounds. At the + // end if the view was partially covered by another interactive + // view we compute the view's interactive region and pick a point + // on its boundary path as regions do not offer APIs to get inner + // points. Note that the the code is optimized to fail early and + // avoid unnecessary allocations plus computations. + + // The current approach has edge cases that may produce false + // positives or false negatives. For example, a portion of the + // view may be covered by an interactive descendant of a + // predecessor, which we do not compute. Also a view may be handling + // raw touch events instead registering click listeners, which + // we cannot compute. Despite these limitations this approach will + // work most of the time and it is a huge improvement over just + // blindly sending the down and up events in the center of the + // view. + + // Cannot click on an unattached view. + if (mAttachInfo == null) { + return false; + } + + // Attached to an invisible window means this view is not visible. + if (mAttachInfo.mWindowVisibility != View.VISIBLE) { + return false; + } + + RectF bounds = mAttachInfo.mTmpTransformRect; + bounds.set(0, 0, getWidth(), getHeight()); + List<RectF> intersections = mAttachInfo.mTmpRectList; + intersections.clear(); + + if (mParent instanceof ViewGroup) { + ViewGroup parentGroup = (ViewGroup) mParent; + if (!parentGroup.translateBoundsAndIntersectionsInWindowCoordinates( + this, bounds, intersections)) { + intersections.clear(); + return false; + } + } + + // Take into account the window location. + final int dx = mAttachInfo.mWindowLeft; + final int dy = mAttachInfo.mWindowTop; + bounds.offset(dx, dy); + offsetRects(intersections, dx, dy); + + if (intersections.isEmpty() && interactiveRegion == null) { + outPoint.set((int) bounds.centerX(), (int) bounds.centerY()); + } else { + // This view is partially covered by other views, then compute + // the not covered region and pick a point on its boundary. + Region region = new Region(); + region.set((int) bounds.left, (int) bounds.top, + (int) bounds.right, (int) bounds.bottom); + + final int intersectionCount = intersections.size(); + for (int i = intersectionCount - 1; i >= 0; i--) { + RectF intersection = intersections.remove(i); + region.op((int) intersection.left, (int) intersection.top, + (int) intersection.right, (int) intersection.bottom, + Region.Op.DIFFERENCE); + } + + // If the view is completely covered, done. + if (region.isEmpty()) { + return false; + } + + // Take into account the interactive portion of the window + // as the rest is covered by other windows. If no such a region + // then the whole window is interactive. + if (interactiveRegion != null) { + region.op(interactiveRegion, Region.Op.INTERSECT); + } + + // If the view is completely covered, done. + if (region.isEmpty()) { + return false; + } + + // Try a shortcut here. + if (region.isRect()) { + Rect regionBounds = mAttachInfo.mTmpInvalRect; + region.getBounds(regionBounds); + outPoint.set(regionBounds.centerX(), regionBounds.centerY()); + return true; + } + + // Get the a point on the region boundary path. + Path path = region.getBoundaryPath(); + PathMeasure pathMeasure = new PathMeasure(path, false); + final float[] coordinates = mAttachInfo.mTmpTransformLocation; + + // Without loss of generality pick a point. + final float point = pathMeasure.getLength() * 0.01f; + if (!pathMeasure.getPosTan(point, coordinates, null)) { + return false; + } + + outPoint.set(Math.round(coordinates[0]), Math.round(coordinates[1])); + } + + return true; + } + + static void offsetRects(List<RectF> rects, float offsetX, float offsetY) { + final int rectCount = rects.size(); + for (int i = 0; i < rectCount; i++) { + RectF intersection = rects.get(i); + intersection.offset(offsetX, offsetY); + } + } + + /** * Returns the delegate for implementing accessibility support via * composition. For more details see {@link AccessibilityDelegate}. * @@ -20154,6 +20286,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final RectF mTmpTransformRect = new RectF(); /** + * Temporary for use in computing hit areas with transformed views + */ + final RectF mTmpTransformRect1 = new RectF(); + + /** + * Temporary list of rectanges. + */ + final List<RectF> mTmpRectList = new ArrayList<>(); + + /** * Temporary for use in transforming invalidation rect */ final Matrix mTmpMatrix = new Matrix(); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index c1e66de..4e1db90 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -771,6 +771,112 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } /** + * Translates the given bounds and intersections from child coordinates to + * local coordinates. In case any interactive sibling of the calling child + * covers the latter, a new intersections is added to the intersection list. + * This method is for the exclusive use by the accessibility layer to compute + * a point where a sequence of down and up events would click on a view. + * + * @param child The child making the call. + * @param bounds The bounds to translate in child coordinates. + * @param intersections The intersections of interactive views covering the child. + * @return True if the bounds and intersections were computed, false otherwise. + */ + boolean translateBoundsAndIntersectionsInWindowCoordinates(View child, + RectF bounds, List<RectF> intersections) { + // Not attached, done. + if (mAttachInfo == null) { + return false; + } + + if (getAlpha() <= 0 || getTransitionAlpha() <= 0 || + getVisibility() != VISIBLE) { + // Cannot click on a view with an invisible predecessor. + return false; + } + + // Compensate for the child transformation. + if (!child.hasIdentityMatrix()) { + Matrix matrix = child.getMatrix(); + matrix.mapRect(bounds); + final int intersectionCount = intersections.size(); + for (int i = 0; i < intersectionCount; i++) { + RectF intersection = intersections.get(i); + matrix.mapRect(intersection); + } + } + + // Translate the bounds from child to parent coordinates. + final int dx = child.mLeft - mScrollX; + final int dy = child.mTop - mScrollY; + bounds.offset(dx, dy); + offsetRects(intersections, dx, dy); + + // If the bounds do not intersect our bounds, done. + if (!bounds.intersects(0, 0, getWidth(), getHeight())) { + return false; + } + + // Check whether any clickable siblings cover the child + // view and if so keep track of the intersections. Also + // respect Z ordering when iterating over children. + ArrayList<View> orderedList = buildOrderedChildList(); + final boolean useCustomOrder = orderedList == null + && isChildrenDrawingOrderEnabled(); + + final int childCount = mChildrenCount; + for (int i = childCount - 1; i >= 0; i--) { + final int childIndex = useCustomOrder + ? getChildDrawingOrder(childCount, i) : i; + final View sibling = (orderedList == null) + ? mChildren[childIndex] : orderedList.get(childIndex); + + // We care only about siblings over the child. + if (sibling == child) { + break; + } + + // If sibling is not interactive we do not care. + if (!sibling.isClickable() && !sibling.isLongClickable()) { + continue; + } + + // Compute the sibling bounds in its coordinates. + RectF siblingBounds = mAttachInfo.mTmpTransformRect1; + siblingBounds.set(0, 0, sibling.getWidth(), sibling.getHeight()); + + // Take into account the sibling transformation matrix. + if (!sibling.hasIdentityMatrix()) { + sibling.getMatrix().mapRect(siblingBounds); + } + + // Offset the sibling to our coordinates. + final int siblingDx = sibling.mLeft - mScrollX; + final int siblingDy = sibling.mTop - mScrollY; + siblingBounds.offset(siblingDx, siblingDy); + + // Compute the intersection between the child and the sibling. + if (siblingBounds.intersect(bounds)) { + // If an interactive sibling completely covers the child, done. + if (siblingBounds.equals(bounds)) { + return false; + } + // Keep track of the intersection rectangle. + RectF intersection = new RectF(siblingBounds); + intersections.add(intersection); + } + } + + if (mParent instanceof ViewGroup) { + ViewGroup parentGroup = (ViewGroup) mParent; + return parentGroup.translateBoundsAndIntersectionsInWindowCoordinates( + this, bounds, intersections); + } + + return true; + } + + /** * Called when a child view has changed whether or not it is tracking transient state. */ public void childHasTransientStateChanged(View child, boolean childHasTransientState) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4299e2e..80b9ade 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6679,12 +6679,12 @@ public final class ViewRootImpl implements ViewParent, public void performAccessibilityAction(long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, - int interogatingPid, long interrogatingTid) { + int interrogatingPid, long interrogatingTid) { ViewRootImpl viewRootImpl = mViewRootImpl.get(); if (viewRootImpl != null && viewRootImpl.mView != null) { viewRootImpl.getAccessibilityInteractionController() .performAccessibilityActionClientThread(accessibilityNodeId, action, arguments, - interactionId, callback, flags, interogatingPid, interrogatingTid); + interactionId, callback, flags, interrogatingPid, interrogatingTid); } else { // We cannot make the call and notify the caller so it does not wait. try { @@ -6696,6 +6696,26 @@ public final class ViewRootImpl implements ViewParent, } @Override + public void computeClickPointInScreen(long accessibilityNodeId, Region interactiveRegion, + int interactionId, IAccessibilityInteractionConnectionCallback callback, + int interrogatingPid, long interrogatingTid, MagnificationSpec spec) { + ViewRootImpl viewRootImpl = mViewRootImpl.get(); + if (viewRootImpl != null && viewRootImpl.mView != null) { + viewRootImpl.getAccessibilityInteractionController() + .computeClickPointInScreenClientThread(accessibilityNodeId, + interactiveRegion, interactionId, callback, interrogatingPid, + interrogatingTid, spec); + } else { + // We cannot make the call and notify the caller so it does not wait. + try { + callback.setComputeClickPointInScreenActionResult(null, interactionId); + } catch (RemoteException re) { + /* best effort - ignore */ + } + } + } + + @Override public void findAccessibilityNodeInfosByViewId(long accessibilityNodeId, String viewId, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java index db78ec5..374f7e0 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -17,6 +17,7 @@ package android.view.accessibility; import android.accessibilityservice.IAccessibilityServiceConnection; +import android.graphics.Point; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -98,6 +99,8 @@ public final class AccessibilityInteractionClient private boolean mPerformAccessibilityActionResult; + private Point mComputeClickPointResult; + private Message mSameThreadMessage; private static final SparseArray<IAccessibilityServiceConnection> sConnectionCache = @@ -519,6 +522,43 @@ public final class AccessibilityInteractionClient return false; } + /** + * Computes a point in screen coordinates where sending a down/up events would + * perform a click on an {@link AccessibilityNodeInfo}. + * + * @param connectionId The id of a connection for interacting with the system. + * @param accessibilityWindowId A unique window id. Use + * {@link android.view.accessibility.AccessibilityNodeInfo#ACTIVE_WINDOW_ID} + * to query the currently active window. + * @param accessibilityNodeId A unique view id or virtual descendant id from + * where to start the search. Use + * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID} + * to start from the root. + * @return Point the click point of null if no such point. + */ + public Point computeClickPointInScreen(int connectionId, int accessibilityWindowId, + long accessibilityNodeId) { + try { + IAccessibilityServiceConnection connection = getConnection(connectionId); + if (connection != null) { + final int interactionId = mInteractionIdCounter.getAndIncrement(); + final boolean success = connection.computeClickPointInScreen( + accessibilityWindowId, accessibilityNodeId, + interactionId, this, Thread.currentThread().getId()); + if (success) { + return getComputeClickPointInScreenResultAndClear(interactionId); + } + } else { + if (DEBUG) { + Log.w(LOG_TAG, "No connection for connection id: " + connectionId); + } + } + } catch (RemoteException re) { + Log.w(LOG_TAG, "Error while calling remote computeClickPointInScreen", re); + } + return null; + } + public void clearCache() { sAccessibilityCache.clear(); } @@ -634,6 +674,34 @@ public final class AccessibilityInteractionClient } /** + * Gets the result of a request to compute a point in screen for clicking on a node. + * + * @param interactionId The interaction id to match the result with the request. + * @return The point or null if no such point. + */ + private Point getComputeClickPointInScreenResultAndClear(int interactionId) { + synchronized (mInstanceLock) { + final boolean success = waitForResultTimedLocked(interactionId); + Point result = success ? mComputeClickPointResult : null; + clearResultLocked(); + return result; + } + } + + /** + * {@inheritDoc} + */ + public void setComputeClickPointInScreenActionResult(Point point, int interactionId) { + synchronized (mInstanceLock) { + if (interactionId > mInteractionId) { + mComputeClickPointResult = point; + mInteractionId = interactionId; + } + mInstanceLock.notifyAll(); + } + } + + /** * Clears the result state. */ private void clearResultLocked() { @@ -641,6 +709,7 @@ public final class AccessibilityInteractionClient mFindAccessibilityNodeInfoResult = null; mFindAccessibilityNodeInfosResult = null; mPerformAccessibilityActionResult = false; + mComputeClickPointResult = null; } /** diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl index faf7789..66a3f46 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -17,6 +17,7 @@ package android.view.accessibility; import android.graphics.Region; +import android.graphics.Point; import android.os.Bundle; import android.view.MagnificationSpec; import android.view.accessibility.AccessibilityNodeInfo; @@ -53,4 +54,8 @@ oneway interface IAccessibilityInteractionConnection { void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid); + + void computeClickPointInScreen(long accessibilityNodeId, in Region bounds, int interactionId, + IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, + long interrogatingTid, in MagnificationSpec spec); } diff --git a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl index c1a3ab7..f480216 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -16,6 +16,7 @@ package android.view.accessibility; +import android.graphics.Point; import android.view.accessibility.AccessibilityNodeInfo; import java.util.List; @@ -51,4 +52,12 @@ oneway interface IAccessibilityInteractionConnectionCallback { * @param interactionId The interaction id to match the result with the request. */ void setPerformAccessibilityActionResult(boolean succeeded, int interactionId); + + /** + * Sets the result of a request to compute a point for clicking in a view. + * + * @param point The point of null if no such point. + * @param interactionId The interaction id to match the result with the request. + */ + void setComputeClickPointInScreenActionResult(in Point point, int interactionId); } |