Fix broken activation of the selected view in accessibility mode.
automerge: ded133c
* commit 'ded133c446fa9d0d23e6bde19a66fb2ce3980491':
Fix broken activation of the selected view in accessibility mode.
This commit is contained in:
@@ -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<AccessibilityWindowInfo> getWindows();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -401,6 +401,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.
|
||||
*/
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
@@ -5804,142 +5822,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
// 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<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}.
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
@@ -779,144 +776,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
// 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<View> 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.
|
||||
*/
|
||||
@@ -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<View> {
|
||||
private List<View> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -6834,26 +6834,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,
|
||||
|
||||
@@ -99,8 +99,6 @@ public final class AccessibilityInteractionClient
|
||||
|
||||
private boolean mPerformAccessibilityActionResult;
|
||||
|
||||
private Point mComputeClickPointResult;
|
||||
|
||||
private Message mSameThreadMessage;
|
||||
|
||||
private static final SparseArray<IAccessibilityServiceConnection> 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();
|
||||
}
|
||||
@@ -673,34 +634,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.
|
||||
*/
|
||||
@@ -709,7 +642,6 @@ public final class AccessibilityInteractionClient
|
||||
mFindAccessibilityNodeInfoResult = null;
|
||||
mFindAccessibilityNodeInfosResult = null;
|
||||
mPerformAccessibilityActionResult = false;
|
||||
mComputeClickPointResult = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -2532,57 +2534,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);
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user