From 6c70290ff0b20329c8f173d5c3423eb83ddc46f1 Mon Sep 17 00:00:00 2001 From: Svetoslav Date: Thu, 9 Oct 2014 18:40:56 -0700 Subject: Adding APIs to enable apps to influence accessibility traversal. Accessibility focus is a token that can be put on any view and is entirely controlled by accessibility services which decide whether to put accessibility focus on a given view and what the traversal strategy is. Currently accessibility service use the view tree structure for traversal which does not always reflect the logical order and the user experience is sub-optimal. In such cases apps should be able to influence how they are traversed for accessibility. This change adds the notion of an accessibilty traversal order. If a view has next in the traversal, an accessibility service has to traverse the content of this view before traversing the content next one. This change will enable accessibility service to move away from using the view tree and use spatial location of the content while app developers can also influence the treversal order. bug:17319923 Change-Id: Ib67ae4577f30b2c96575c71538dc1afdea08bfd3 --- core/java/android/view/View.java | 134 ++++++++++++++++ .../view/accessibility/AccessibilityNodeInfo.java | 168 ++++++++++++++++++--- core/java/android/widget/RemoteViews.java | 20 +++ 3 files changed, 301 insertions(+), 21 deletions(-) (limited to 'core/java') diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e4f95a4..1d09696 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3109,6 +3109,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private MatchLabelForPredicate mMatchLabelForPredicate; /** + * Specifies a view before which this one is visited in accessibility traversal. + */ + private int mAccessibilityTraversalBeforeId = NO_ID; + + /** + * Specifies a view after which this one is visited in accessibility traversal. + */ + private int mAccessibilityTraversalAfterId = NO_ID; + + /** * Predicate for matching a view by its id. */ private MatchIdPredicate mMatchIdPredicate; @@ -3888,6 +3898,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case com.android.internal.R.styleable.View_contentDescription: setContentDescription(a.getString(attr)); break; + case com.android.internal.R.styleable.View_accessibilityTraversalBefore: + setAccessibilityTraversalBefore(a.getResourceId(attr, NO_ID)); + break; + case com.android.internal.R.styleable.View_accessibilityTraversalAfter: + setAccessibilityTraversalAfter(a.getResourceId(attr, NO_ID)); + break; case com.android.internal.R.styleable.View_labelFor: setLabelFor(a.getResourceId(attr, NO_ID)); break; @@ -5611,6 +5627,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (rootView == null) { rootView = this; } + View label = rootView.findLabelForView(this, mID); if (label != null) { info.setLabeledBy(label); @@ -5639,6 +5656,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } + if (mAccessibilityTraversalBeforeId != View.NO_ID) { + View rootView = getRootView(); + if (rootView == null) { + rootView = this; + } + View next = rootView.findViewInsideOutShouldExist(this, + mAccessibilityTraversalBeforeId); + if (next != null) { + info.setTraversalBefore(next); + } + } + + if (mAccessibilityTraversalAfterId != View.NO_ID) { + View rootView = getRootView(); + if (rootView == null) { + rootView = this; + } + View next = rootView.findViewInsideOutShouldExist(this, + mAccessibilityTraversalAfterId); + if (next != null) { + info.setTraversalAfter(next); + } + } + info.setVisibleToUser(isVisibleToUser()); info.setPackageName(mContext.getPackageName()); @@ -6043,6 +6084,94 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Sets the id of a view before which this one is visited in accessibility traversal. + * A screen-reader must visit the content of this view before the content of the one + * it precedes. For example, if view B is set to be before view A, then a screen-reader + * will traverse the entire content of B before traversing the entire content of A, + * regardles of what traversal strategy it is using. + *

+ * Views that do not have specified before/after relationships are traversed in order + * determined by the screen-reader. + *

+ *

+ * Setting that this view is before a view that is not important for accessibility + * or if this view is not important for accessibility will have no effect as the + * screen-reader is not aware of unimportant views. + *

+ * + * @param beforeId The id of a view this one precedes in accessibility traversal. + * + * @attr ref android.R.styleable#View_accessibilityTraversalBefore + * + * @see #setImportantForAccessibility(int) + */ + @RemotableViewMethod + public void setAccessibilityTraversalBefore(int beforeId) { + if (mAccessibilityTraversalBeforeId == beforeId) { + return; + } + mAccessibilityTraversalBeforeId = beforeId; + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + + /** + * Gets the id of a view before which this one is visited in accessibility traversal. + * + * @return The id of a view this one precedes in accessibility traversal if + * specified, otherwise {@link #NO_ID}. + * + * @see #setAccessibilityTraversalBefore(int) + */ + public int getAccessibilityTraversalBefore() { + return mAccessibilityTraversalBeforeId; + } + + /** + * Sets the id of a view after which this one is visited in accessibility traversal. + * A screen-reader must visit the content of the other view before the content of this + * one. For example, if view B is set to be after view A, then a screen-reader + * will traverse the entire content of A before traversing the entire content of B, + * regardles of what traversal strategy it is using. + *

+ * Views that do not have specified before/after relationships are traversed in order + * determined by the screen-reader. + *

+ *

+ * Setting that this view is after a view that is not important for accessibility + * or if this view is not important for accessibility will have no effect as the + * screen-reader is not aware of unimportant views. + *

+ * + * @param afterId The id of a view this one succedees in accessibility traversal. + * + * @attr ref android.R.styleable#View_accessibilityTraversalAfter + * + * @see #setImportantForAccessibility(int) + */ + @RemotableViewMethod + public void setAccessibilityTraversalAfter(int afterId) { + if (mAccessibilityTraversalAfterId == afterId) { + return; + } + mAccessibilityTraversalAfterId = afterId; + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); + } + + /** + * Gets the id of a view after which this one is visited in accessibility traversal. + * + * @return The id of a view this one succeedes in accessibility traversal if + * specified, otherwise {@link #NO_ID}. + * + * @see #setAccessibilityTraversalAfter(int) + */ + public int getAccessibilityTraversalAfter() { + return mAccessibilityTraversalAfterId; + } + + /** * Gets the id of a view for which this view serves as a label for * accessibility purposes. * @@ -6061,11 +6190,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ @RemotableViewMethod public void setLabelFor(int id) { + if (mLabelForId == id) { + return; + } mLabelForId = id; if (mLabelForId != View.NO_ID && mID == View.NO_ID) { mID = generateViewId(); } + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } /** diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 3987fbc..b5afdf7 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -547,6 +547,8 @@ public class AccessibilityNodeInfo implements Parcelable { private long mParentNodeId = ROOT_NODE_ID; private long mLabelForId = ROOT_NODE_ID; private long mLabeledById = ROOT_NODE_ID; + private long mTraversalBefore = ROOT_NODE_ID; + private long mTraversalAfter = ROOT_NODE_ID; private int mBooleanProperties; private final Rect mBoundsInParent = new Rect(); @@ -1046,6 +1048,126 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets the node before which this one is visited during traversal. A screen-reader + * must visit the content of this node before the content of the one it precedes. + * + * @return The succeeding node if such or null. + * + * @see #setTraversalBefore(android.view.View) + * @see #setTraversalBefore(android.view.View, int) + */ + public AccessibilityNodeInfo getTraversalBefore() { + enforceSealed(); + return getNodeForAccessibilityId(mTraversalBefore); + } + + /** + * Sets the view before whose node this one should be visited during traversal. A + * screen-reader must visit the content of this node before the content of the one + * it precedes. + *

+ * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

+ * + * @param view The view providing the preceding node. + * + * @see #getTraversalBefore() + */ + public void setTraversalBefore(View view) { + setTraversalBefore(view, UNDEFINED_ITEM_ID); + } + + /** + * Sets the node before which this one is visited during traversal. A screen-reader + * must visit the content of this node before the content of the one it precedes. + * The successor is a virtual descendant of the given root. If + * virtualDescendantId equals to {@link View#NO_ID} the root is set + * as the successor. + *

+ * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report them selves as a tree of virtual views, thus conveying their + * logical structure. + *

+ *

+ * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

+ * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual descendant. + */ + public void setTraversalBefore(View root, int virtualDescendantId) { + enforceNotSealed(); + final int rootAccessibilityViewId = (root != null) + ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; + mTraversalBefore = makeNodeId(rootAccessibilityViewId, virtualDescendantId); + } + + /** + * Gets the node after which this one is visited in accessibility traversal. + * A screen-reader must visit the content of the other node before the content + * of this one. + * + * @return The succeeding node if such or null. + * + * @see #setTraversalAfter(android.view.View) + * @see #setTraversalAfter(android.view.View, int) + */ + public AccessibilityNodeInfo getTraversalAfter() { + enforceSealed(); + return getNodeForAccessibilityId(mTraversalAfter); + } + + /** + * Sets the view whose node is visited after this one in accessibility traversal. + * A screen-reader must visit the content of the other node before the content + * of this one. + *

+ * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

+ * + * @param view The previous view. + * + * @see #getTraversalAfter() + */ + public void setTraversalAfter(View view) { + setTraversalAfter(view, UNDEFINED_ITEM_ID); + } + + /** + * Sets the node after which this one is visited in accessibility traversal. + * A screen-reader must visit the content of the other node before the content + * of this one. If virtualDescendantId equals to {@link View#NO_ID} + * the root is set as the predecessor. + *

+ * A virtual descendant is an imaginary View that is reported as a part of the view + * hierarchy for accessibility purposes. This enables custom views that draw complex + * content to report them selves as a tree of virtual views, thus conveying their + * logical structure. + *

+ *

+ * Note: Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. + * This class is made immutable before being delivered to an AccessibilityService. + *

+ * + * @param root The root of the virtual subtree. + * @param virtualDescendantId The id of the virtual descendant. + */ + public void setTraversalAfter(View root, int virtualDescendantId) { + enforceNotSealed(); + final int rootAccessibilityViewId = (root != null) + ? root.getAccessibilityViewId() : UNDEFINED_ITEM_ID; + mTraversalAfter = makeNodeId(rootAccessibilityViewId, virtualDescendantId); + } + + /** * Sets the maximum text length, or -1 for no limit. *

* Typically used to indicate that an editable text field has a limit on @@ -1229,13 +1351,7 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getParent() { enforceSealed(); - if (!canPerformRequestOverConnection(mParentNodeId)) { - return null; - } - AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mParentNodeId, false, FLAG_PREFETCH_PREDECESSORS - | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + return getNodeForAccessibilityId(mParentNodeId); } /** @@ -2055,13 +2171,7 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getLabelFor() { enforceSealed(); - if (!canPerformRequestOverConnection(mLabelForId)) { - return null; - } - AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabelForId, false, FLAG_PREFETCH_PREDECESSORS - | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + return getNodeForAccessibilityId(mLabelForId); } /** @@ -2113,13 +2223,7 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getLabeledBy() { enforceSealed(); - if (!canPerformRequestOverConnection(mLabeledById)) { - return null; - } - AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); - return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, - mWindowId, mLabeledById, false, FLAG_PREFETCH_PREDECESSORS - | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + return getNodeForAccessibilityId(mLabeledById); } /** @@ -2453,6 +2557,9 @@ public class AccessibilityNodeInfo implements Parcelable { parcel.writeLong(mParentNodeId); parcel.writeLong(mLabelForId); parcel.writeLong(mLabeledById); + parcel.writeLong(mTraversalBefore); + parcel.writeLong(mTraversalAfter); + parcel.writeInt(mConnectionId); final LongArray childIds = mChildNodeIds; @@ -2571,6 +2678,8 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = other.mParentNodeId; mLabelForId = other.mLabelForId; mLabeledById = other.mLabeledById; + mTraversalBefore = other.mTraversalBefore; + mTraversalAfter = other.mTraversalAfter; mWindowId = other.mWindowId; mConnectionId = other.mConnectionId; mBoundsInParent.set(other.mBoundsInParent); @@ -2633,6 +2742,9 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = parcel.readLong(); mLabelForId = parcel.readLong(); mLabeledById = parcel.readLong(); + mTraversalBefore = parcel.readLong(); + mTraversalAfter = parcel.readLong(); + mConnectionId = parcel.readInt(); final int childrenSize = parcel.readInt(); @@ -2725,6 +2837,8 @@ public class AccessibilityNodeInfo implements Parcelable { mParentNodeId = ROOT_NODE_ID; mLabelForId = ROOT_NODE_ID; mLabeledById = ROOT_NODE_ID; + mTraversalBefore = ROOT_NODE_ID; + mTraversalAfter = ROOT_NODE_ID; mWindowId = UNDEFINED_ITEM_ID; mConnectionId = UNDEFINED_CONNECTION_ID; mMaxTextLength = -1; @@ -2911,6 +3025,8 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; accessibilityViewId: " + getAccessibilityViewId(mSourceNodeId)); builder.append("; virtualDescendantId: " + getVirtualDescendantId(mSourceNodeId)); builder.append("; mParentNodeId: " + mParentNodeId); + builder.append("; traversalBefore: ").append(mTraversalBefore); + builder.append("; traversalAfter: ").append(mTraversalAfter); int granularities = mMovementGranularities; builder.append("; MovementGranularities: ["); @@ -2963,6 +3079,16 @@ public class AccessibilityNodeInfo implements Parcelable { return builder.toString(); } + private AccessibilityNodeInfo getNodeForAccessibilityId(long accessibilityId) { + if (!canPerformRequestOverConnection(accessibilityId)) { + return null; + } + AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); + return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, + mWindowId, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS + | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS); + } + /** * A class defining an action that can be performed on an {@link AccessibilityNodeInfo}. * Each action has a unique id that is mandatory and optional data. diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 80f364b..dd7fa18 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -2528,6 +2528,26 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}. + * + * @param viewId The id of the view whose before view in accessibility traversal to set. + * @param nextId The id of the next in the accessibility traversal. + **/ + public void setAccessibilityTraversalBefore(int viewId, int nextId) { + setInt(viewId, "setAccessibilityTraversalBefore", nextId); + } + + /** + * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}. + * + * @param viewId The id of the view whose after view in accessibility traversal to set. + * @param nextId The id of the next in the accessibility traversal. + **/ + public void setAccessibilityTraversalAfter(int viewId, int nextId) { + setInt(viewId, "setAccessibilityTraversalAfter", nextId); + } + + /** * Equivalent to calling View.setLabelFor(int). * * @param viewId The id of the view whose property to set. -- cgit v1.1