Skip to content

Commit a1dc761

Browse files
committed
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
1 parent 1bc1b8a commit a1dc761

File tree

6 files changed

+124
-10
lines changed

6 files changed

+124
-10
lines changed

api/current.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25234,6 +25234,8 @@ package android.view.accessibility {
2523425234
field public static final int ACTION_NEXT_HTML_ELEMENT = 1024; // 0x400
2523525235
field public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 512; // 0x200
2523625236
field public static final int ACTION_PREVIOUS_HTML_ELEMENT = 2048; // 0x800
25237+
field public static final int ACTION_SCROLL_BACKWARD = 8192; // 0x2000
25238+
field public static final int ACTION_SCROLL_FORWARD = 4096; // 0x1000
2523725239
field public static final int ACTION_SELECT = 4; // 0x4
2523825240
field public static final android.os.Parcelable.Creator CREATOR;
2523925241
field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2

core/java/android/view/accessibility/AccessibilityNodeInfo.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,16 @@ public class AccessibilityNodeInfo implements Parcelable {
204204
*/
205205
public static final int ACTION_PREVIOUS_HTML_ELEMENT = 0x00000800;
206206

207+
/**
208+
* Action to scroll the node content forward.
209+
*/
210+
public static final int ACTION_SCROLL_FORWARD = 0x00001000;
211+
212+
/**
213+
* Action to scroll the node content backward.
214+
*/
215+
public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
216+
207217
/**
208218
* Argument for which movement granularity to be used when traversing the node text.
209219
* <p>
@@ -569,6 +579,16 @@ public void addChild(View root, int virtualDescendantId) {
569579
* @see AccessibilityNodeInfo#ACTION_CLEAR_FOCUS
570580
* @see AccessibilityNodeInfo#ACTION_SELECT
571581
* @see AccessibilityNodeInfo#ACTION_CLEAR_SELECTION
582+
* @see AccessibilityNodeInfo#ACTION_ACCESSIBILITY_FOCUS
583+
* @see AccessibilityNodeInfo#ACTION_CLEAR_ACCESSIBILITY_FOCUS
584+
* @see AccessibilityNodeInfo#ACTION_CLICK
585+
* @see AccessibilityNodeInfo#ACTION_LONG_CLICK
586+
* @see AccessibilityNodeInfo#ACTION_NEXT_AT_MOVEMENT_GRANULARITY
587+
* @see AccessibilityNodeInfo#ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
588+
* @see AccessibilityNodeInfo#ACTION_NEXT_HTML_ELEMENT
589+
* @see AccessibilityNodeInfo#ACTION_PREVIOUS_HTML_ELEMENT
590+
* @see AccessibilityNodeInfo#ACTION_SCROLL_FORWARD
591+
* @see AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD
572592
*/
573593
public int getActions() {
574594
return mActions;
@@ -1578,6 +1598,10 @@ private static String getActionSymbolicName(int action) {
15781598
return "ACTION_NEXT_HTML_ELEMENT";
15791599
case ACTION_PREVIOUS_HTML_ELEMENT:
15801600
return "ACTION_PREVIOUS_HTML_ELEMENT";
1601+
case ACTION_SCROLL_FORWARD:
1602+
return "ACTION_SCROLL_FORWARD";
1603+
case ACTION_SCROLL_BACKWARD:
1604+
return "ACTION_SCROLL_BACKWARD";
15811605
default:
15821606
throw new IllegalArgumentException("Unknown action: " + action);
15831607
}

core/java/android/widget/AbsListView.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,6 +1355,33 @@ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
13551355
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
13561356
super.onInitializeAccessibilityNodeInfo(info);
13571357
info.setClassName(AbsListView.class.getName());
1358+
if (getFirstVisiblePosition() > 0) {
1359+
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
1360+
}
1361+
if (getLastVisiblePosition() < getCount() - 1) {
1362+
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
1363+
}
1364+
}
1365+
1366+
@Override
1367+
public boolean performAccessibilityAction(int action, Bundle arguments) {
1368+
switch (action) {
1369+
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
1370+
if (getLastVisiblePosition() < getCount() - 1) {
1371+
final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1372+
smoothScrollBy(viewportHeight, PositionScroller.SCROLL_DURATION);
1373+
return true;
1374+
}
1375+
} return false;
1376+
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
1377+
if (mFirstPosition > 0) {
1378+
final int viewportHeight = getHeight() - mListPadding.top - mListPadding.bottom;
1379+
smoothScrollBy(-viewportHeight, PositionScroller.SCROLL_DURATION);
1380+
return true;
1381+
}
1382+
} return false;
1383+
}
1384+
return super.performAccessibilityAction(action, arguments);
13581385
}
13591386

13601387
/**

core/java/android/widget/HorizontalScrollView.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import android.content.res.TypedArray;
2121
import android.graphics.Canvas;
2222
import android.graphics.Rect;
23+
import android.os.Bundle;
2324
import android.util.AttributeSet;
2425
import android.view.FocusFinder;
2526
import android.view.InputDevice;
@@ -736,11 +737,43 @@ protected void onOverScrolled(int scrollX, int scrollY,
736737
awakenScrollBars();
737738
}
738739

740+
@Override
741+
public boolean performAccessibilityAction(int action, Bundle arguments) {
742+
switch (action) {
743+
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
744+
final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight;
745+
final int targetScrollX = Math.min(mScrollX + viewportWidth, getScrollRange());
746+
if (targetScrollX != mScrollX) {
747+
smoothScrollTo(targetScrollX, 0);
748+
return true;
749+
}
750+
} return false;
751+
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
752+
final int viewportWidth = getWidth() - mPaddingLeft - mPaddingRight;
753+
final int targetScrollX = Math.max(0, mScrollX - viewportWidth);
754+
if (targetScrollX != mScrollX) {
755+
smoothScrollTo(targetScrollX, 0);
756+
return true;
757+
}
758+
} return false;
759+
}
760+
return super.performAccessibilityAction(action, arguments);
761+
}
762+
739763
@Override
740764
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
741765
super.onInitializeAccessibilityNodeInfo(info);
742766
info.setClassName(HorizontalScrollView.class.getName());
743-
info.setScrollable(getScrollRange() > 0);
767+
final int scrollRange = getScrollRange();
768+
if (scrollRange > 0) {
769+
info.setScrollable(true);
770+
if (mScrollX > 0) {
771+
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
772+
}
773+
if (mScrollX < scrollRange) {
774+
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
775+
}
776+
}
744777
}
745778

746779
@Override

core/java/android/widget/ScrollView.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import android.content.res.TypedArray;
2323
import android.graphics.Canvas;
2424
import android.graphics.Rect;
25+
import android.os.Bundle;
2526
import android.os.StrictMode;
2627
import android.util.AttributeSet;
2728
import android.view.FocusFinder;
@@ -739,11 +740,43 @@ protected void onOverScrolled(int scrollX, int scrollY,
739740
awakenScrollBars();
740741
}
741742

743+
@Override
744+
public boolean performAccessibilityAction(int action, Bundle arguments) {
745+
switch (action) {
746+
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
747+
final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
748+
final int targetScrollY = Math.min(mScrollY + viewportHeight, getScrollRange());
749+
if (targetScrollY != mScrollY) {
750+
smoothScrollTo(0, targetScrollY);
751+
return true;
752+
}
753+
} return false;
754+
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
755+
final int viewportHeight = getHeight() - mPaddingBottom - mPaddingTop;
756+
final int targetScrollY = Math.max(mScrollY - viewportHeight, 0);
757+
if (targetScrollY != mScrollY) {
758+
smoothScrollTo(0, targetScrollY);
759+
return true;
760+
}
761+
} return false;
762+
}
763+
return super.performAccessibilityAction(action, arguments);
764+
}
765+
742766
@Override
743767
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
744768
super.onInitializeAccessibilityNodeInfo(info);
745769
info.setClassName(ScrollView.class.getName());
746-
info.setScrollable(getScrollRange() > 0);
770+
final int scrollRange = getScrollRange();
771+
if (scrollRange > 0) {
772+
info.setScrollable(true);
773+
if (mScrollY > 0) {
774+
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
775+
}
776+
if (mScrollY < scrollRange) {
777+
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
778+
}
779+
}
747780
}
748781

749782
@Override

services/java/com/android/server/accessibility/AccessibilityManagerService.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1701,14 +1701,9 @@ final class SecurityPolicy {
17011701
| AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
17021702
| AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
17031703
| AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT
1704-
| AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT;
1705-
1706-
private static final int VALID_GRANULARITIES =
1707-
AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
1708-
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
1709-
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
1710-
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
1711-
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE;
1704+
| AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT
1705+
| AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
1706+
| AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
17121707

17131708
private static final int RETRIEVAL_ALLOWING_EVENT_TYPES =
17141709
AccessibilityEvent.TYPE_VIEW_CLICKED

0 commit comments

Comments
 (0)