Use accessibility action for touch exploration

Explore-By-Touch has been dispatching touch events to the screen
rather than using the accessibility API. This was intended as a
workaround for apps that did not properly handle accessibility,
but the workaround itself has been causing bugs in corner cases
where properly accessible Views are partially covered by windows.

This CL first tries to dispatch a click action, and falls back on
the touch dispatch only if the click action fails.

Bug: 35200501
Bug: 26216304
Bug: 20665958
Bug: 34949365
Bug: 34844480
Bug: 29535082

Test: Poking around with first party apps and TalkBack works fine.
This behavior isn't covered by automated testing.

Change-Id: I9cc18399d8f40f7381dfcbef91b5991b711bb7f1
This commit is contained in:
Phil Weaver
2017-02-17 14:55:13 -08:00
parent d666953f57
commit 0adfbd33c8
2 changed files with 48 additions and 11 deletions

View File

@@ -912,8 +912,21 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
*/
// TODO: (multi-display) Make sure this works for multiple displays.
boolean getAccessibilityFocusClickPointInScreen(Point outPoint) {
return getInteractionBridgeLocked()
.getAccessibilityFocusClickPointInScreenNotLocked(outPoint);
return getInteractionBridge().getAccessibilityFocusClickPointInScreenNotLocked(outPoint);
}
/**
* Perform an accessibility action on the view that currently has accessibility focus.
* Has no effect if no item has accessibility focus, if the item with accessibility
* focus does not expose the specified action, or if the action fails.
*
* @param actionId The id of the action to perform.
*
* @return {@code true} if the action was performed. {@code false} if it was not.
*/
public boolean performActionOnAccessibilityFocusedItem(
AccessibilityNodeInfo.AccessibilityAction action) {
return getInteractionBridge().performActionOnAccessibilityFocusedItemNotLocked(action);
}
/**
@@ -1029,11 +1042,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
onUserStateChangedLocked(userState);
}
private InteractionBridge getInteractionBridgeLocked() {
if (mInteractionBridge == null) {
mInteractionBridge = new InteractionBridge();
private InteractionBridge getInteractionBridge() {
synchronized (mLock) {
if (mInteractionBridge == null) {
mInteractionBridge = new InteractionBridge();
}
return mInteractionBridge;
}
return mInteractionBridge;
}
private boolean notifyGestureLocked(int gestureId, boolean isDefault) {
@@ -2274,11 +2289,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
case MSG_CLEAR_ACCESSIBILITY_FOCUS: {
final int windowId = msg.arg1;
InteractionBridge bridge;
synchronized (mLock) {
bridge = getInteractionBridgeLocked();
}
bridge.clearAccessibilityFocusNotLocked(windowId);
getInteractionBridge().clearAccessibilityFocusNotLocked(windowId);
} break;
case MSG_SEND_SERVICES_STATE_CHANGED_TO_CLIENTS: {
@@ -4051,6 +4062,24 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub {
}
}
/**
* Perform an accessibility action on the view that currently has accessibility focus.
* Has no effect if no item has accessibility focus, if the item with accessibility
* focus does not expose the specified action, or if the action fails.
*
* @param actionId The id of the action to perform.
*
* @return {@code true} if the action was performed. {@code false} if it was not.
*/
public boolean performActionOnAccessibilityFocusedItemNotLocked(
AccessibilityNodeInfo.AccessibilityAction action) {
AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked();
if ((focus == null) || !focus.getActionList().contains(action)) {
return false;
}
return focus.performAction(action.getId());
}
public boolean getAccessibilityFocusClickPointInScreenNotLocked(Point outPoint) {
AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked();
if (focus == null) {

View File

@@ -29,6 +29,7 @@ import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import java.util.Arrays;
@@ -407,6 +408,13 @@ class TouchExplorer implements EventStreamTransformation, AccessibilityGestureDe
mSendTouchInteractionEndDelayed.forceSendAndRemove();
}
// Try to use the standard accessibility API to click
if (mAms.performActionOnAccessibilityFocusedItem(
AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK)) {
return true;
}
Slog.e(LOG_TAG, "ACTION_CLICK failed. Dispatching motion events to simulate click.");
final int pointerIndex = event.getActionIndex();
final int pointerId = event.getPointerId(pointerIndex);