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
This commit is contained in:
Svetoslav Ganov
2012-05-10 04:14:53 -07:00
parent 1bc1b8a5b8
commit a1dc761c83
6 changed files with 124 additions and 10 deletions

View File

@@ -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

View File

@@ -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.
* <p>
@@ -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);
}

View File

@@ -1355,6 +1355,33 @@ public abstract class AbsListView extends AdapterView<ListAdapter> 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);
}
/**

View File

@@ -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

View File

@@ -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

View File

@@ -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