diff options
author | Svet Ganov <svetoslavganov@google.com> | 2014-09-03 21:33:00 -0700 |
---|---|---|
committer | Svet Ganov <svetoslavganov@google.com> | 2014-09-07 23:36:20 -0700 |
commit | 7498efdc5e163d6b4a11db941c7d13c169d37284 (patch) | |
tree | 2d94b4e0fe85ea98a438ecca6184d878ee5e61bd /services/accessibility | |
parent | df11867be3f60b6bd5e24afca7820c29a28c85c7 (diff) | |
download | frameworks_base-7498efdc5e163d6b4a11db941c7d13c169d37284.zip frameworks_base-7498efdc5e163d6b4a11db941c7d13c169d37284.tar.gz frameworks_base-7498efdc5e163d6b4a11db941c7d13c169d37284.tar.bz2 |
Clicking on partially coverd views by other views or windows.
In touch exploration mode an accessibility service can move
accessibility focus in response to user gestures. In this case
when the user double-taps the system is sending down and up
events at the center of the acessibility focused view. This
works fine until the clicked view's center is covered by another
clickable view. In such a scenario the user thinks he is clicking
on one view but the click is handled by another. Terrible.
This change solves the problem of clicking on the wrong view
and also solves the problem of clicking on the wrong window.
The key idea is that when the system detects a double tap or
a double tap and hold it asks the accessibility focused node
(if such) to compute a point at which a click can be performed.
In respinse to that the node is asking the source view to
compute this.
If a view is partially covered by siblings or siblings of
predecessors that are clickable, the click point will be
properly computed to ensure the click occurs on the desired
view. The click point is also bounded in the interactive
part of the host window.
The current approach has rare 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 (we check only siblings of
predecessors). Also a view may be handling raw touch events
instead of 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.
Note that the additional computational complexity is incurred
only when the user wants to click on the accessibility focused
view which is very a rare event and this is a good tradeoff.
bug:15696993
Change-Id: I85927a77d6c24f7550b0d5f9f762722a8230830f
Diffstat (limited to 'services/accessibility')
-rw-r--r-- | services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java | 113 | ||||
-rw-r--r-- | services/accessibility/java/com/android/server/accessibility/TouchExplorer.java | 48 |
2 files changed, 115 insertions, 46 deletions
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index a43a2a6..ebe21ff 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -746,14 +746,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } /** - * Gets the bounds of the accessibility focus in the active window. + * Gets a point within the accessibility focused node where we can send down + * and up events to perform a click. * - * @param outBounds The output to which to write the focus bounds. - * @return Whether accessibility focus was found and the bounds are populated. + * @param outPoint The click point to populate. + * @return Whether accessibility a click point was found and set. */ // TODO: (multi-display) Make sure this works for multiple displays. - boolean getAccessibilityFocusBounds(Rect outBounds) { - return getInteractionBridgeLocked().getAccessibilityFocusBoundsNotLocked(outBounds); + boolean getAccessibilityFocusClickPointInScreen(Point outPoint) { + return getInteractionBridgeLocked() + .getAccessibilityFocusClickPointInScreenNotLocked(outPoint); } /** @@ -2196,8 +2198,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { connection.findAccessibilityNodeInfosByViewId(accessibilityNodeId, viewIdResName, - partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, - interrogatingTid, spec); + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2248,8 +2250,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, - partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, - interrogatingTid, spec); + partialInteractiveRegion, interactionId, callback, mFetchFlags, + interrogatingPid, interrogatingTid, spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2352,8 +2354,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final long identityToken = Binder.clearCallingIdentity(); MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { - connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, interactionId, - callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); + connection.findFocus(accessibilityNodeId, focusType, partialInteractiveRegion, + interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, + spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2403,8 +2406,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { final long identityToken = Binder.clearCallingIdentity(); MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId); try { - connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, interactionId, - callback, mFetchFlags, interrogatingPid, interrogatingTid, spec); + connection.focusSearch(accessibilityNodeId, direction, partialInteractiveRegion, + interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, + spec); return true; } catch (RemoteException re) { if (DEBUG) { @@ -2460,6 +2464,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { return true; } + @Override public boolean performGlobalAction(int action) { synchronized (mLock) { // We treat calls from a profile as if made by its parent as profiles @@ -2501,6 +2506,57 @@ 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.getCallingUserId()); + 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) { @@ -3119,30 +3175,43 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { } } - public boolean getAccessibilityFocusBoundsNotLocked(Rect outBounds) { + public boolean getAccessibilityFocusClickPointInScreenNotLocked(Point outPoint) { AccessibilityNodeInfo focus = getAccessibilityFocusNotLocked(); if (focus == null) { return false; } synchronized (mLock) { - focus.getBoundsInScreen(outBounds); + Point point = mClient.computeClickPointInScreen(mConnectionId, + focus.getWindowId(), focus.getSourceNodeId()); + + if (point == null) { + return false; + } MagnificationSpec spec = getCompatibleMagnificationSpecLocked(focus.getWindowId()); if (spec != null && !spec.isNop()) { - outBounds.offset((int) -spec.offsetX, (int) -spec.offsetY); - outBounds.scale(1 / spec.scale); + point.offset((int) -spec.offsetX, (int) -spec.offsetY); + point.x = (int) (point.x * (1 / spec.scale)); + point.y = (int) (point.y * (1 / spec.scale)); } - // Clip to the window rectangle. + // Make sure the point is within the window. Rect windowBounds = mTempRect; getActiveWindowBounds(windowBounds); - outBounds.intersect(windowBounds); + if (!windowBounds.contains(point.x, point.y)) { + return false; + } - // Clip to the screen rectangle. - mDefaultDisplay.getRealSize(mTempPoint); - outBounds.intersect(0, 0, mTempPoint.x, mTempPoint.y); + // Make sure the point is within the screen. + Point screenSize = mTempPoint; + mDefaultDisplay.getRealSize(screenSize); + if (point.x < 0 || point.x > screenSize.x + || point.y < 0 || point.y > screenSize.y) { + return false; + } + outPoint.set(point.x, point.y); return true; } } diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java index ac0ca0a..9e63433 100644 --- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java @@ -24,6 +24,7 @@ import android.gesture.GesturePoint; import android.gesture.GestureStore; import android.gesture.GestureStroke; import android.gesture.Prediction; +import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; @@ -171,6 +172,9 @@ class TouchExplorer implements EventStreamTransformation { // Temporary rectangle to avoid instantiation. private final Rect mTempRect = new Rect(); + // Temporary point to avoid instantiation. + private final Point mTempPoint = new Point(); + // Context in which this explorer operates. private final Context mContext; @@ -1157,18 +1161,18 @@ class TouchExplorer implements EventStreamTransformation { mInjectedPointerTracker.getLastInjectedHoverEventForClick(); if (lastExploreEvent == null) { // No last touch explored event but there is accessibility focus in - // the active window. We click in the middle of the focus bounds. - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); + // the active window. We click in the focus bounds. + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } else { // Out of luck - do nothing. return; } } else { // If the click is within the active window but not within the - // accessibility focus bounds we click in the focus center. + // accessibility focus bounds we click in the focus bounds. final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); @@ -1176,12 +1180,10 @@ class TouchExplorer implements EventStreamTransformation { if (mLastTouchedWindowId == mAms.getActiveWindowId()) { mAms.getActiveWindowBounds(activeWindowBounds); if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - if (!focusBounds.contains(clickLocationX, clickLocationY)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } } } @@ -1330,18 +1332,18 @@ class TouchExplorer implements EventStreamTransformation { mInjectedPointerTracker.getLastInjectedHoverEventForClick(); if (lastExploreEvent == null) { // No last touch explored event but there is accessibility focus in - // the active window. We click in the middle of the focus bounds. - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); + // the active window. We click in the focus bounds. + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } else { // Out of luck - do nothing. return; } } else { // If the click is within the active window but not within the - // accessibility focus bounds we click in the focus center. + // accessibility focus bounds we click in the focus bounds. final int lastExplorePointerIndex = lastExploreEvent.getActionIndex(); clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex); clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex); @@ -1349,12 +1351,10 @@ class TouchExplorer implements EventStreamTransformation { if (mLastTouchedWindowId == mAms.getActiveWindowId()) { mAms.getActiveWindowBounds(activeWindowBounds); if (activeWindowBounds.contains(clickLocationX, clickLocationY)) { - Rect focusBounds = mTempRect; - if (mAms.getAccessibilityFocusBounds(focusBounds)) { - if (!focusBounds.contains(clickLocationX, clickLocationY)) { - clickLocationX = focusBounds.centerX(); - clickLocationY = focusBounds.centerY(); - } + Point point = mTempPoint; + if (mAms.getAccessibilityFocusClickPointInScreen(point)) { + clickLocationX = point.x; + clickLocationY = point.y; } } } |