From a1dc761c8322355eb1bb71d3d6c9c603c1d1fc0f Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Thu, 10 May 2012 04:14:53 -0700 Subject: [PATCH] Adding scroll actions to accessibility node info. 1. Scrolling actions are crucial for enabling a gesture based traversal of the UI and specifically scrollable containers especially lists and anything backed by an adapter. Since accessibility focus can land only attached views, it cannot visit views for adapter items not shown on the screen. Auto scrolling the list as a result of putting access focus ot a list item does not work well since the user may get trapped in a long list. Adding an accessibility node provider to emit virtual views for one view before the first and one after the last is complex and suffers the limitation of trapping the user. Accessibility service need an explicit scroll actions which may be performed upon an explicit user action. Hence, the user is informed for the start/end of the visible part of the list and he makes a deliberate choice to scroll. This will benefit also people developing Braille devices since they can scroll the content without telling the user to stop using the Braille controller and take the device out of his pocket to scroll and go back to the Braille controller. NOTE: Without these action large portions of the screen will be hard to access since users will have to touch and explore to find and scroll the list. Change-Id: Iafcf54d4967893205872b3649025a4e347a299ed --- api/current.txt | 2 ++ .../accessibility/AccessibilityNodeInfo.java | 24 +++++++++++++ core/java/android/widget/AbsListView.java | 27 ++++++++++++++ .../android/widget/HorizontalScrollView.java | 35 ++++++++++++++++++- core/java/android/widget/ScrollView.java | 35 ++++++++++++++++++- .../AccessibilityManagerService.java | 11 ++---- 6 files changed, 124 insertions(+), 10 deletions(-) diff --git a/api/current.txt b/api/current.txt index 4b10577f3c69e..c909ba0c4f743 100644 --- a/api/current.txt +++ b/api/current.txt @@ -25234,6 +25234,8 @@ package android.view.accessibility { field public static final int ACTION_NEXT_HTML_ELEMENT = 1024; // 0x400 field public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 512; // 0x200 field public static final int ACTION_PREVIOUS_HTML_ELEMENT = 2048; // 0x800 + field public static final int ACTION_SCROLL_BACKWARD = 8192; // 0x2000 + field public static final int ACTION_SCROLL_FORWARD = 4096; // 0x1000 field public static final int ACTION_SELECT = 4; // 0x4 field public static final android.os.Parcelable.Creator CREATOR; field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2 diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 6b14ba5e37230..0517d4b353746 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -204,6 +204,16 @@ public class AccessibilityNodeInfo implements Parcelable { */ public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800; + /** + * Action to scroll the node content forward. + */ + public static final int ACTION_SCROLL_FORWARD = 0x00001000; + + /** + * Action to scroll the node content backward. + */ + public static final int ACTION_SCROLL_BACKWARD = 0x00002000; + /** * Argument for which movement granularity to be used when traversing the node text. *

@@ -569,6 +579,16 @@ public class AccessibilityNodeInfo implements Parcelable { * @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS * @see AccessibilityNodeInfo#ACTION_SELECT * @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION + * @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS + * @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS + * @see AccessibilityNodeInfo#ACTION_CLICK + * @see AccessibilityNodeInfo#ACTION_LONG_CLICK + * @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY + * @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY + * @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT + * @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT + * @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD + * @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD */ public int getActions() { return mActions; @@ -1578,6 +1598,10 @@ public class AccessibilityNodeInfo implements Parcelable { return "ACTION_NEXT_HTML_ELEMENT"; case ACTION_PREVIOUS_HTML_ELEMENT: return "ACTION_PREVIOUS_HTML_ELEMENT"; + case ACTION_SCROLL_FORWARD: + return "ACTION_SCROLL_FORWARD"; + case ACTION_SCROLL_BACKWARD: + return "ACTION_SCROLL_BACKWARD"; default: throw new IllegalArgumentException("Unknown action: " + action); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 3aafba5997346..c4e1bf5391d72 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -1355,6 +1355,33 @@ public abstract class AbsListView extends AdapterView implements Te public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(AbsListView.class.getName()); + if (getFirstVisiblePosition() > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + if (getLastVisiblePosition() < getCount() - 1) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + if (getLastVisiblePosition() < getCount() - 1) { + final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; + smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + if (mFirstPosition > 0) { + final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom; + smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION); + return true; + } + } return false; + } + return super.performAccessibilityAction(action, arguments); } /** diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 1986450d89588..ffabd1d981df7 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.Bundle; import android.util.AttributeSet; import android.view.FocusFinder; import android.view.InputDevice; @@ -736,11 +737,43 @@ public class HorizontalScrollView extends FrameLayout { awakenScrollBars(); } + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight; + final int targetScrollX = Math.min(mScrollX + viewportWidth, getScrollRange()); + if (targetScrollX != mScrollX) { + smoothScrollTo(targetScrollX, 0); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight; + final int targetScrollX = Math.max(0, mScrollX - viewportWidth); + if (targetScrollX != mScrollX) { + smoothScrollTo(targetScrollX, 0); + return true; + } + } return false; + } + return super.performAccessibilityAction(action, arguments); + } + @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(HorizontalScrollView.class.getName()); - info.setScrollable(getScrollRange() > 0); + final int scrollRange = getScrollRange(); + if (scrollRange > 0) { + info.setScrollable(true); + if (mScrollX > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + if (mScrollX < scrollRange) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + } } @Override diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index f912c66256219..b398ce4a0e16d 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; +import android.os.Bundle; import android.os.StrictMode; import android.util.AttributeSet; import android.view.FocusFinder; @@ -739,11 +740,43 @@ public class ScrollView extends FrameLayout { awakenScrollBars(); } + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + switch (action) { + case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: { + final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop; + final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange()); + if (targetScrollY != mScrollY) { + smoothScrollTo(0, targetScrollY); + return true; + } + } return false; + case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: { + final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop; + final int targetScrollY = Math.max(mScrollY - viewportHeight, 0); + if (targetScrollY != mScrollY) { + smoothScrollTo(0, targetScrollY); + return true; + } + } return false; + } + return super.performAccessibilityAction(action, arguments); + } + @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(ScrollView.class.getName()); - info.setScrollable(getScrollRange() > 0); + final int scrollRange = getScrollRange(); + if (scrollRange > 0) { + info.setScrollable(true); + if (mScrollY > 0) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); + } + if (mScrollY < scrollRange) { + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); + } + } } @Override diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index df7bbf2815f6a..0c6d85dbb1e12 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1701,14 +1701,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub | AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY | AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY | AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT - | AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT; - - private static final int VALID_GRANULARITIES = - AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH - | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE; + | AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT + | AccessibilityNodeInfo.ACTION_SCROLL_FORWARD + | AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD; private static final int RETRIEVAL_ALLOWING_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED