From ded133c446fa9d0d23e6bde19a66fb2ce3980491 Mon Sep 17 00:00:00 2001 From: Svetoslav Date: Fri, 30 Jan 2015 20:28:41 -0800 Subject: [PATCH] Fix broken activation of the selected view in accessibility mode. We were using an approximation to determine where to send a pair of down and up events to click on the view that has accessibility focus. We were doing reverse computation to figuring out which portion of the view is not covered by interactive views and get a point in this region. However, determining whether a view is interactive is not feasible in general since for example may override onTouchEvent. This results in views not being activated or which is worse wrong views being activated. This change swithes to a new approach to activate views in accessibility mode which is guaranteed to always work except the very rare case of a view that overrides dispatchTouchEvent (which developers shouldn't be doing). The new approach is to flag the down and up events pair sent by the touch explorer as targeting the accessibility focused view. Such events are dispatched such that views predecessors of the accessibility focus do not handle them guaranteeing that these events reach the accessibiliy focused view. Once the accessibiliy focused view gets such an event it clears the flag and the event is dispatched following the normal event dispatch semantics. The new approach is semantically equivalent to requesting the view to perform a click accessiblitiy action but is more generic as it is not affected by views not implementing click action support correctly. bug:18986806 bug:18889611 Change-Id: Id4b7b886c9fd34f7eb11e606636d8e3bab122869 --- .../IAccessibilityServiceConnection.aidl | 4 - .../view/AccessibilityInteractionController.java | 95 --------- core/java/android/view/MotionEvent.java | 31 +++ core/java/android/view/View.java | 169 +++------------- core/java/android/view/ViewGroup.java | 225 ++------------------- core/java/android/view/ViewRootImpl.java | 22 +- .../AccessibilityInteractionClient.java | 68 ------- .../IAccessibilityInteractionConnection.aidl | 4 - ...AccessibilityInteractionConnectionCallback.aidl | 8 - .../accessibility/AccessibilityManagerService.java | 87 ++------ .../server/accessibility/TouchExplorer.java | 36 ++-- 11 files changed, 125 insertions(+), 624 deletions(-) diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl index 27a03b69706a..5f7a17d33bd1 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -54,10 +54,6 @@ 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 getWindows(); diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java index 5e05683be3d0..68ad782ddbd9 100644 --- a/core/java/android/view/AccessibilityInteractionController.java +++ b/core/java/android/view/AccessibilityInteractionController.java @@ -636,95 +636,6 @@ 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) { @@ -1201,7 +1112,6 @@ final class AccessibilityInteractionController { 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); @@ -1223,8 +1133,6 @@ final class AccessibilityInteractionController { 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); } @@ -1252,9 +1160,6 @@ 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/MotionEvent.java b/core/java/android/view/MotionEvent.java index 1c5c41c74905..5e45c8fe10dd 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -402,6 +402,23 @@ public final class MotionEvent extends InputEvent implements Parcelable { public static final int FLAG_TAINTED = 0x80000000; /** + * Private flag indicating that this event was synthesized by the system and + * should be delivered to the accessibility focused view first. When being + * dispatched such an event is not handled by predecessors of the accessibility + * focused view and after the event reaches that view the flag is cleared and + * normal event dispatch is performed. This ensures that the platform can click + * on any view that has accessibility focus which is semantically equivalent to + * asking the view to perform a click accessibility action but more generic as + * views not implementing click action correctly can still be activated. + * + * @hide + * @see #isTargetAccessibilityFocus() + * @see #setTargetAccessibilityFocus(boolean) + */ + public static final int FLAG_TARGET_ACCESSIBILITY_FOCUS = 0x40000000; + + + /** * Flag indicating the motion event intersected the top edge of the screen. */ public static final int EDGE_TOP = 0x00000001; @@ -1766,6 +1783,20 @@ public final class MotionEvent extends InputEvent implements Parcelable { nativeSetFlags(mNativePtr, tainted ? flags | FLAG_TAINTED : flags & ~FLAG_TAINTED); } + /** @hide */ + public final boolean isTargetAccessibilityFocus() { + final int flags = getFlags(); + return (flags & FLAG_TARGET_ACCESSIBILITY_FOCUS) != 0; + } + + /** @hide */ + public final void setTargetAccessibilityFocus(boolean targetsFocus) { + final int flags = getFlags(); + nativeSetFlags(mNativePtr, targetsFocus + ? flags | FLAG_TARGET_ACCESSIBILITY_FOCUS + : flags & ~FLAG_TARGET_ACCESSIBILITY_FOCUS); + } + /** * Returns the time (in ms) when the user originally pressed down to start * a stream of position events. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index ed75de39234f..6928b2c83fe9 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5553,12 +5553,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * Gets the location of this view in screen coordintates. + * Gets the location of this view in screen coordinates. * * @param outRect The output location * @hide */ public void getBoundsOnScreen(Rect outRect) { + getBoundsOnScreen(outRect, false); + } + + /** + * Gets the location of this view in screen coordinates. + * + * @param outRect The output location + * @param clipToParent Whether to clip child bounds to the parent ones. + * @hide + */ + public void getBoundsOnScreen(Rect outRect, boolean clipToParent) { if (mAttachInfo == null) { return; } @@ -5578,6 +5589,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, position.offset(-parentView.mScrollX, -parentView.mScrollY); + if (clipToParent) { + position.left = Math.max(position.left, 0); + position.top = Math.max(position.top, 0); + position.right = Math.min(position.right, parentView.getWidth()); + position.bottom = Math.min(position.bottom, parentView.getHeight()); + } + if (!parentView.hasIdentityMatrix()) { parentView.getMatrix().mapRect(position); } @@ -5609,7 +5627,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, getDrawingRect(bounds); info.setBoundsInParent(bounds); - getBoundsOnScreen(bounds); + getBoundsOnScreen(bounds, true); info.setBoundsInScreen(bounds); ViewParent parent = getParentForAccessibility(); @@ -5805,142 +5823,6 @@ 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 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); - } - - // Take into account the window bounds. - final View root = getRootView(); - if (root != null) { - region.op(dx, dy, root.getWidth() + dx, root.getHeight() + dy, 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 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}. * @@ -8555,6 +8437,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { + // If the event should be handled by accessibility focus first. + if (event.isTargetAccessibilityFocus()) { + // We don't have focus or no virtual descendant has it, do not handle the event. + if (!isAccessibilityFocused() && !(getViewRootImpl() != null && getViewRootImpl() + .getAccessibilityFocusedHost() == this)) { + return false; + } + // We have focus and got the event, then use normal event dispatch. + event.setTargetAccessibilityFocus(false); + } + boolean result = false; if (mInputEventConsistencyVerifier != null) { diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 0b1a2d4da926..fd50c4da058a 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -474,9 +474,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @ViewDebug.ExportedProperty(category = "layout") private int mChildCountWithTransientState = 0; - // Iterator over the children in decreasing Z order (top children first). - private OrderedChildIterator mOrderedChildIterator; - /** * Currently registered axes for nested scrolling. Flag set consisting of * {@link #SCROLL_AXIS_HORIZONTAL} {@link #SCROLL_AXIS_VERTICAL} or {@link #SCROLL_AXIS_NONE} @@ -780,144 +777,6 @@ 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 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; - } - - // Clip the bounds by our bounds. - bounds.left = Math.max(bounds.left, 0); - bounds.top = Math.max(bounds.top, 0); - bounds.right = Math.min(bounds.right, getWidth()); - bounds.bottom = Math.min(bounds.bottom, getHeight()); - - Iterator iterator = obtainOrderedChildIterator(); - while (iterator.hasNext()) { - View sibling = iterator.next(); - - // We care only about siblings over the child. - if (sibling == child) { - break; - } - - // Ignore invisible views as they are not interactive. - if (!isVisible(sibling)) { - continue; - } - - // Compute the sibling bounds in its coordinates. - RectF siblingBounds = mAttachInfo.mTmpTransformRect1; - siblingBounds.set(0, 0, sibling.getWidth(), sibling.getHeight()); - - // Translate the sibling bounds to our coordinates. - offsetChildRectToMyCoords(siblingBounds, sibling); - - // Compute the intersection between the child and the sibling. - if (siblingBounds.intersect(bounds)) { - // Conservatively we consider an overlapping sibling to be - // interactive and ignore it. This is not ideal as if the - // sibling completely covers the view despite handling no - // touch events we will not be able to click on the view. - intersections.add(siblingBounds); - } - } - - releaseOrderedChildIterator(); - - if (mParent instanceof ViewGroup) { - ViewGroup parentGroup = (ViewGroup) mParent; - return parentGroup.translateBoundsAndIntersectionsInWindowCoordinates( - this, bounds, intersections); - } - - return true; - } - - private void offsetChildRectToMyCoords(RectF rect, View child) { - if (!child.hasIdentityMatrix()) { - child.getMatrix().mapRect(rect); - } - final int childDx = child.mLeft - mScrollX; - final int childDy = child.mTop - mScrollY; - rect.offset(childDx, childDy); - } - - private static boolean isVisible(View view) { - return (view.getAlpha() > 0 && view.getTransitionAlpha() > 0 && - view.getVisibility() == VISIBLE); - } - - /** - * Obtains the iterator to traverse the children in a descending Z order. - * Only one party can use the iterator at any given time and you cannot - * modify the children while using this iterator. Acquisition if already - * obtained is an error. - * - * @return The child iterator. - */ - OrderedChildIterator obtainOrderedChildIterator() { - if (mOrderedChildIterator == null) { - mOrderedChildIterator = new OrderedChildIterator(); - } else if (mOrderedChildIterator.isInitialized()) { - throw new IllegalStateException("Already obtained"); - } - mOrderedChildIterator.initialize(); - return mOrderedChildIterator; - } - - /** - * Releases the iterator to traverse the children in a descending Z order. - * Release if not obtained is an error. - */ - void releaseOrderedChildIterator() { - if (mOrderedChildIterator == null || !mOrderedChildIterator.isInitialized()) { - throw new IllegalStateException("Not obtained"); - } - mOrderedChildIterator.release(); - } - - /** * Called when a child view has changed whether or not it is tracking transient state. */ public void childHasTransientStateChanged(View child, boolean childHasTransientState) { @@ -2072,6 +1931,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } + // Whether this event should be handled by the accessibility focus first. + final boolean targetAccessibilityFocus = ev.isTargetAccessibilityFocus(); + boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); @@ -2088,19 +1950,24 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager // Check for interception. final boolean intercepted; - if (actionMasked == MotionEvent.ACTION_DOWN - || mFirstTouchTarget != null) { - final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; - if (!disallowIntercept) { - intercepted = onInterceptTouchEvent(ev); - ev.setAction(action); // restore action in case it was changed + if (!targetAccessibilityFocus) { + if (actionMasked == MotionEvent.ACTION_DOWN + || mFirstTouchTarget != null) { + final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; + if (!disallowIntercept) { + intercepted = onInterceptTouchEvent(ev); + ev.setAction(action); // restore action in case it was changed + } else { + intercepted = false; + } } else { - intercepted = false; + // There are no touch targets and this action is not an initial down + // so this view group continues to intercept touches. + intercepted = true; } } else { - // There are no touch targets and this action is not an initial down - // so this view group continues to intercept touches. - intercepted = true; + // If event should reach the accessibility focus first, do not intercept it. + intercepted = false; } // Check for cancelation. @@ -2114,7 +1981,8 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (!canceled && !intercepted) { if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) - || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { + || actionMasked == MotionEvent.ACTION_HOVER_MOVE + || targetAccessibilityFocus) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; @@ -7403,57 +7271,4 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager canvas.drawLines(sDebugLines, paint); } - - private final class OrderedChildIterator implements Iterator { - private List mOrderedChildList; - private boolean mUseCustomOrder; - private int mCurrentIndex; - private boolean mInitialized; - - public void initialize() { - mOrderedChildList = buildOrderedChildList(); - mUseCustomOrder = (mOrderedChildList == null) - && isChildrenDrawingOrderEnabled(); - mCurrentIndex = mChildrenCount - 1; - mInitialized = true; - } - - public void release() { - if (mOrderedChildList != null) { - mOrderedChildList.clear(); - } - mUseCustomOrder = false; - mCurrentIndex = 0; - mInitialized = false; - } - - public boolean isInitialized() { - return mInitialized; - } - - @Override - public boolean hasNext() { - return (mCurrentIndex >= 0); - } - - @Override - public View next() { - if (!hasNext()) { - throw new NoSuchElementException("No such element"); - } - return getChild(mCurrentIndex--); - } - - private View getChild(int index) { - final int childIndex = mUseCustomOrder - ? getChildDrawingOrder(mChildrenCount, index) : index; - return (mOrderedChildList == null) - ? mChildren[childIndex] : mOrderedChildList.get(childIndex); - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - } } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 87d9a581feb8..e4d82b16d185 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2703,7 +2703,7 @@ public final class ViewRootImpl implements ViewParent, final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider(); if (provider == null) { - host.getBoundsOnScreen(bounds); + host.getBoundsOnScreen(bounds, true); } else if (mAccessibilityFocusedVirtualView != null) { mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds); } else { @@ -6835,26 +6835,6 @@ 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 374f7e064409..cefd34d7fefd 100644 --- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java +++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java @@ -99,8 +99,6 @@ public final class AccessibilityInteractionClient private boolean mPerformAccessibilityActionResult; - private Point mComputeClickPointResult; - private Message mSameThreadMessage; private static final SparseArray sConnectionCache = @@ -522,43 +520,6 @@ 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(); } @@ -674,34 +635,6 @@ 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() { @@ -709,7 +642,6 @@ 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 66a3f46f2498..cecc4af49e2e 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnection.aidl @@ -54,8 +54,4 @@ 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 f4802168d194..42ae1b32382b 100644 --- a/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl +++ b/core/java/android/view/accessibility/IAccessibilityInteractionConnectionCallback.aidl @@ -52,12 +52,4 @@ 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); } diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index bbf3644a9aaa..93c65f392020 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -170,6 +170,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { private final Rect mTempRect = new Rect(); + private final Rect mTempRect1 = new Rect(); + private final Point mTempPoint = new Point(); private final PackageManager mPackageManager; @@ -2533,57 +2535,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } @Override - public boolean computeClickPointInScreen(int accessibilityWindowId, - long accessibilityNodeId, int interactionId, - IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) - throws RemoteException { - final int resolvedWindowId; - IAccessibilityInteractionConnection connection = null; - Region partialInteractiveRegion = mTempRegion; - synchronized (mLock) { - // We treat calls from a profile as if made by its parent as profiles - // share the accessibility state of the parent. The call below - // performs the current profile parent resolution. - final int resolvedUserId = mSecurityPolicy - .resolveCallingUserIdEnforcingPermissionsLocked( - UserHandle.USER_CURRENT); - if (resolvedUserId != mCurrentUserId) { - return false; - } - resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId); - final boolean permissionGranted = - mSecurityPolicy.canRetrieveWindowContentLocked(this); - if (!permissionGranted) { - return false; - } else { - connection = getConnectionLocked(resolvedWindowId); - if (connection == null) { - return false; - } - } - if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked( - resolvedWindowId, partialInteractiveRegion)) { - partialInteractiveRegion = null; - } - } - final int interrogatingPid = Binder.getCallingPid(); - final long identityToken = Binder.clearCallingIdentity(); - MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); - try { - connection.computeClickPointInScreen(accessibilityNodeId, partialInteractiveRegion, - interactionId, callback, interrogatingPid, interrogatingTid, spec); - return true; - } catch (RemoteException re) { - if (DEBUG) { - Slog.e(LOG_TAG, "Error computeClickPointInScreen()."); - } - } finally { - Binder.restoreCallingIdentity(identityToken); - } - return false; - } - - @Override public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { mSecurityPolicy.enforceCallingPermission(Manifest.permission.DUMP, FUNCTION_DUMP); synchronized (mLock) { @@ -3236,38 +3187,36 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } synchronized (mLock) { - Point point = mClient.computeClickPointInScreen(mConnectionId, - focus.getWindowId(), focus.getSourceNodeId()); + Rect boundsInScreen = mTempRect; + focus.getBoundsInScreen(boundsInScreen); - if (point == null) { + // Clip to the window bounds. + Rect windowBounds = mTempRect1; + getWindowBounds(focus.getWindowId(), windowBounds); + boundsInScreen.intersect(windowBounds); + if (boundsInScreen.isEmpty()) { return false; } + // Apply magnification if needed. MagnificationSpec spec = getCompatibleMagnificationSpecLocked(focus.getWindowId()); if (spec != null && !spec.isNop()) { - point.offset((int) -spec.offsetX, (int) -spec.offsetY); - point.x = (int) (point.x * (1 / spec.scale)); - point.y = (int) (point.y * (1 / spec.scale)); + boundsInScreen.offset((int) -spec.offsetX, (int) -spec.offsetY); + boundsInScreen.scale(1 / spec.scale); } - // Make sure the point is within the window. - Rect windowBounds = mTempRect; - getWindowBounds(focus.getWindowId(), windowBounds); - if (!windowBounds.contains(point.x, point.y)) { - return false; - } - - // Make sure the point is within the screen. + // Clip to the screen bounds. Point screenSize = mTempPoint; mDefaultDisplay.getRealSize(screenSize); - if (point.x < 0 || point.x > screenSize.x - || point.y < 0 || point.y > screenSize.y) { + boundsInScreen.intersect(0, 0, screenSize.x, screenSize.y); + if (boundsInScreen.isEmpty()) { return false; } - outPoint.set(point.x, point.y); - return true; + outPoint.set(boundsInScreen.centerX(), boundsInScreen.centerY()); } + + return true; } private AccessibilityNodeInfo getAccessibilityFocusNotLocked() { diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index b9ed89b27c10..f18b5ef338aa 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -77,6 +77,10 @@ class TouchExplorer implements EventStreamTransformation { private static final int STATE_DELEGATING = 0x00000004; private static final int STATE_GESTURE_DETECTING = 0x00000005; + private static final int CLICK_LOCATION_NONE = 0; + private static final int CLICK_LOCATION_ACCESSIBILITY_FOCUS = 1; + private static final int CLICK_LOCATION_LAST_TOUCH_EXPLORED = 2; + // The maximum of the cosine between the vectors of two moving // pointers so they can be considered moving in the same direction. private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4) @@ -942,12 +946,16 @@ class TouchExplorer implements EventStreamTransformation { * * @param prototype The prototype from which to create the injected events. * @param policyFlags The policy flags associated with the event. + * @param targetAccessibilityFocus Whether the event targets the accessibility focus. */ - private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) { + private void sendActionDownAndUp(MotionEvent prototype, int policyFlags, + boolean targetAccessibilityFocus) { // Tap with the pointer that last explored. final int pointerId = prototype.getPointerId(prototype.getActionIndex()); final int pointerIdBits = (1 << pointerId); + prototype.setTargetAccessibilityFocus(targetAccessibilityFocus); sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags); + prototype.setTargetAccessibilityFocus(targetAccessibilityFocus); sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); } @@ -1155,7 +1163,8 @@ class TouchExplorer implements EventStreamTransformation { final int pointerIndex = secondTapUp.findPointerIndex(pointerId); Point clickLocation = mTempPoint; - if (!computeClickLocation(clickLocation)) { + final int result = computeClickLocation(clickLocation); + if (result == CLICK_LOCATION_NONE) { return; } @@ -1171,7 +1180,8 @@ class TouchExplorer implements EventStreamTransformation { secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties, coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0, secondTapUp.getSource(), secondTapUp.getFlags()); - sendActionDownAndUp(event, policyFlags); + final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS); + sendActionDownAndUp(event, policyFlags, targetAccessibilityFocus); event.recycle(); } @@ -1216,7 +1226,7 @@ class TouchExplorer implements EventStreamTransformation { MAX_DRAGGING_ANGLE_COS); } - private boolean computeClickLocation(Point outLocation) { + private int computeClickLocation(Point outLocation) { MotionEvent lastExploreEvent = mInjectedPointerTracker.getLastInjectedHoverEventForClick(); if (lastExploreEvent != null) { final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); @@ -1224,14 +1234,17 @@ class TouchExplorer implements EventStreamTransformation { outLocation.y = (int) lastExploreEvent.getY(lastExplorePointerIndex); if (!mAms.accessibilityFocusOnlyInActiveWindow() || mLastTouchedWindowId == mAms.getActiveWindowId()) { - mAms.getAccessibilityFocusClickPointInScreen(outLocation); + if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) { + return CLICK_LOCATION_ACCESSIBILITY_FOCUS; + } else { + return CLICK_LOCATION_LAST_TOUCH_EXPLORED; + } } - return true; } if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) { - return true; + return CLICK_LOCATION_ACCESSIBILITY_FOCUS; } - return false; + return CLICK_LOCATION_NONE; } /** @@ -1310,14 +1323,13 @@ class TouchExplorer implements EventStreamTransformation { return; } - int clickLocationX; - int clickLocationY; - final int pointerId = mEvent.getPointerId(mEvent.getActionIndex()); final int pointerIndex = mEvent.findPointerIndex(pointerId); Point clickLocation = mTempPoint; - if (!computeClickLocation(clickLocation)) { + final int result = computeClickLocation(clickLocation); + + if (result == CLICK_LOCATION_NONE) { return; } -- 2.11.0