am f48a2d30: Merge "Add accessibility actions for text editing."

# Via Android (Google) Code Review (1) and Svetoslav (1)
* commit 'f48a2d30c5d5f7c167310ff9ca1cd2310121be7c':
  Add accessibility actions for text editing.
This commit is contained in:
Svetoslav
2013-01-30 23:55:44 -08:00
committed by Android Git Automerger
5 changed files with 274 additions and 45 deletions

View File

@@ -1562,9 +1562,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
int mAccessibilityViewId = NO_ID;
/**
* @hide
*/
private int mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
/**
@@ -2516,8 +2513,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* The undefined cursor position.
*
* @hide
*/
private static final int ACCESSIBILITY_CURSOR_POSITION_UNDEFINED = -1;
public static final int ACCESSIBILITY_CURSOR_POSITION_UNDEFINED = -1;
/**
* Indicates that the screen has changed state and is now off.
@@ -7009,21 +7008,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (arguments != null) {
final int granularity = arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
return nextAtGranularity(granularity);
final boolean extendSelection = arguments.getBoolean(
AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
return nextAtGranularity(granularity, extendSelection);
}
} break;
case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
if (arguments != null) {
final int granularity = arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);
return previousAtGranularity(granularity);
final boolean extendSelection = arguments.getBoolean(
AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);
return previousAtGranularity(granularity, extendSelection);
}
} break;
}
return false;
}
private boolean nextAtGranularity(int granularity) {
private boolean nextAtGranularity(int granularity, boolean extendSelection) {
CharSequence text = getIterableTextForAccessibility();
if (text == null || text.length() == 0) {
return false;
@@ -7032,21 +7035,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (iterator == null) {
return false;
}
final int current = getAccessibilityCursorPosition();
int current = getAccessibilitySelectionEnd();
if (current == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
current = 0;
}
final int[] range = iterator.following(current);
if (range == null) {
return false;
}
final int start = range[0];
final int end = range[1];
setAccessibilityCursorPosition(end);
if (extendSelection && isAccessibilitySelectionExtendable()) {
int selectionStart = getAccessibilitySelectionStart();
if (selectionStart == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
selectionStart = start;
}
setAccessibilitySelection(selectionStart, end);
} else {
setAccessibilitySelection(end, end);
}
sendViewTextTraversedAtGranularityEvent(
AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY,
granularity, start, end);
return true;
}
private boolean previousAtGranularity(int granularity) {
private boolean previousAtGranularity(int granularity, boolean extendSelection) {
CharSequence text = getIterableTextForAccessibility();
if (text == null || text.length() == 0) {
return false;
@@ -7055,15 +7069,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (iterator == null) {
return false;
}
int current = getAccessibilityCursorPosition();
int current = getAccessibilitySelectionStart();
if (current == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
current = text.length();
setAccessibilityCursorPosition(current);
} else if (granularity == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER) {
// When traversing by character we always put the cursor after the character
// to ease edit and have to compensate before asking the for previous segment.
current--;
setAccessibilityCursorPosition(current);
}
final int[] range = iterator.preceding(current);
if (range == null) {
@@ -7071,11 +7079,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
final int start = range[0];
final int end = range[1];
// Always put the cursor after the character to ease edit.
if (granularity == AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER) {
setAccessibilityCursorPosition(end);
if (extendSelection && isAccessibilitySelectionExtendable()) {
int selectionEnd = getAccessibilitySelectionEnd();
if (selectionEnd == ACCESSIBILITY_CURSOR_POSITION_UNDEFINED) {
selectionEnd = end;
}
setAccessibilitySelection(start, selectionEnd);
} else {
setAccessibilityCursorPosition(start);
setAccessibilitySelection(start, start);
}
sendViewTextTraversedAtGranularityEvent(
AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
@@ -7095,17 +7106,39 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
* Gets whether accessibility selection can be extended.
*
* @return If selection is extensible.
*
* @hide
*/
public int getAccessibilityCursorPosition() {
public boolean isAccessibilitySelectionExtendable() {
return false;
}
/**
* @hide
*/
public int getAccessibilitySelectionStart() {
return mAccessibilityCursorPosition;
}
/**
* @hide
*/
public void setAccessibilityCursorPosition(int position) {
mAccessibilityCursorPosition = position;
public int getAccessibilitySelectionEnd() {
return getAccessibilitySelectionStart();
}
/**
* @hide
*/
public void setAccessibilitySelection(int start, int end) {
if (start >= 0 && start == end && end <= getIterableTextForAccessibility().length()) {
mAccessibilityCursorPosition = start;
} else {
mAccessibilityCursorPosition = ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
}
}
private void sendViewTextTraversedAtGranularityEvent(int action, int granularity,

View File

@@ -131,16 +131,22 @@ public class AccessibilityNodeInfo implements Parcelable {
* at a given movement granularity. For example, move to the next character,
* word, etc.
* <p>
* <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br>
* <strong>Example:</strong>
* <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
* {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
* <strong>Example:</strong> Move to the previous character and do not extend selection.
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
* AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
* arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
* false);
* info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments);
* </code></pre></p>
* </p>
*
* @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
* @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
*
* @see #setMovementGranularities(int)
* @see #getMovementGranularities()
*
@@ -157,17 +163,23 @@ public class AccessibilityNodeInfo implements Parcelable {
* at a given movement granularity. For example, move to the next character,
* word, etc.
* <p>
* <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<br>
* <strong>Example:</strong>
* <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<,
* {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br>
* <strong>Example:</strong> Move to the next character and do not extend selection.
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
* AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER);
* arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
* false);
* info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
* arguments);
* </code></pre></p>
* </p>
*
* @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT
* @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN
*
* @see #setMovementGranularities(int)
* @see #getMovementGranularities()
*
@@ -219,6 +231,41 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
/**
* Action to copy the current selection to the clipboard.
*/
public static final int ACTION_COPY = 0x00004000;
/**
* Action to paste the current clipboard content.
*/
public static final int ACTION_PASTE = 0x00008000;
/**
* Action to cut the current selection and place it to the clipboard.
*/
public static final int ACTION_CUT = 0x00010000;
/**
* Action to set the selection. Performing this action with no arguments
* clears the selection.
* <p>
* <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_SELECTION_START_INT},
* {@link #ACTION_ARGUMENT_SELECTION_END_INT}<br>
* <strong>Example:</strong>
* <code><pre><p>
* Bundle arguments = new Bundle();
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1);
* arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2);
* info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
* </code></pre></p>
* </p>
*
* @see #ACTION_ARGUMENT_SELECTION_START_INT
* @see #ACTION_ARGUMENT_SELECTION_END_INT
*/
public static final int ACTION_SET_SELECTION = 0x00020000;
/**
* Argument for which movement granularity to be used when traversing the node text.
* <p>
@@ -226,9 +273,12 @@ public class AccessibilityNodeInfo implements Parcelable {
* <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY},
* {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
* </p>
*
* @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY
* @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
*/
public static final String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT =
"ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
"ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
/**
* Argument for which HTML element to get moving to the next/previous HTML element.
@@ -237,9 +287,51 @@ public class AccessibilityNodeInfo implements Parcelable {
* <strong>Actions:</strong> {@link #ACTION_NEXT_HTML_ELEMENT},
* {@link #ACTION_PREVIOUS_HTML_ELEMENT}
* </p>
*
* @see #ACTION_NEXT_HTML_ELEMENT
* @see #ACTION_PREVIOUS_HTML_ELEMENT
*/
public static final String ACTION_ARGUMENT_HTML_ELEMENT_STRING =
"ACTION_ARGUMENT_HTML_ELEMENT_STRING";
"ACTION_ARGUMENT_HTML_ELEMENT_STRING";
/**
* Argument for whether when moving at granularity to extend the selection
* or to move it otherwise.
* <p>
* <strong>Type:</strong> boolean<br>
* <strong>Actions:</strong> {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY},
* {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY}
* </p>
*
* @see #ACTION_NEXT_AT_MOVEMENT_GRANULARITY
* @see #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
*/
public static final String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN =
"ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
/**
* Argument for specifying the selection start.
* <p>
* <strong>Type:</strong> int<br>
* <strong>Actions:</strong> {@link #ACTION_SET_SELECTION}
* </p>
*
* @see #ACTION_SET_SELECTION
*/
public static final String ACTION_ARGUMENT_SELECTION_START_INT =
"ACTION_ARGUMENT_SELECTION_START_INT";
/**
* Argument for specifying the selection end.
* <p>
* <strong>Type:</strong> int<br>
* <strong>Actions:</strong> {@link #ACTION_SET_SELECTION}
* </p>
*
* @see #ACTION_SET_SELECTION
*/
public static final String ACTION_ARGUMENT_SELECTION_END_INT =
"ACTION_ARGUMENT_SELECTION_END_INT";
/**
* The input focus.

View File

@@ -7985,6 +7985,80 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
| AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
}
if (isFocused()) {
if (canSelectText()) {
info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
}
if (canCopy()) {
info.addAction(AccessibilityNodeInfo.ACTION_COPY);
}
if (canPaste()) {
info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
}
if (canCut()) {
info.addAction(AccessibilityNodeInfo.ACTION_CUT);
}
}
}
@Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_COPY: {
if (isFocused() && canCopy()) {
if (onTextContextMenuItem(ID_COPY)) {
notifyAccessibilityStateChanged();
return true;
}
}
} return false;
case AccessibilityNodeInfo.ACTION_PASTE: {
if (isFocused() && canPaste()) {
if (onTextContextMenuItem(ID_PASTE)) {
notifyAccessibilityStateChanged();
return true;
}
}
} return false;
case AccessibilityNodeInfo.ACTION_CUT: {
if (isFocused() && canCut()) {
if (onTextContextMenuItem(ID_CUT)) {
notifyAccessibilityStateChanged();
return true;
}
}
} return false;
case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
if (isFocused() && canSelectText()) {
final int start = (arguments != null) ? arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
final int end = (arguments != null) ? arguments.getInt(
AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
CharSequence text = getIterableTextForAccessibility();
if (text == null) {
return false;
}
// No arguments clears the selection.
if (start == end && end == -1) {
Selection.removeSelection((Spannable) text);
notifyAccessibilityStateChanged();
return true;
}
if (start >= 0 && start <= end && end <= text.length()) {
Selection.setSelection((Spannable) text, start, end);
// Make sure selection mode is engaged.
if (mEditor != null) {
mEditor.startSelectionActionMode();
}
notifyAccessibilityStateChanged();
return true;
}
}
} return false;
default: {
return super.performAccessibilityAction(action, arguments);
}
}
}
@Override
@@ -8554,32 +8628,51 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @hide
*/
@Override
public int getAccessibilityCursorPosition() {
public int getAccessibilitySelectionStart() {
if (TextUtils.isEmpty(getContentDescription())) {
final int selectionEnd = getSelectionEnd();
if (selectionEnd >= 0) {
return selectionEnd;
final int selectionStart = getSelectionStart();
if (selectionStart >= 0) {
return selectionStart;
}
}
return super.getAccessibilityCursorPosition();
return ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
}
/**
* @hide
*/
public boolean isAccessibilitySelectionExtendable() {
return true;
}
/**
* @hide
*/
@Override
public void setAccessibilityCursorPosition(int index) {
if (getAccessibilityCursorPosition() == index) {
public int getAccessibilitySelectionEnd() {
if (TextUtils.isEmpty(getContentDescription())) {
final int selectionEnd = getSelectionEnd();
if (selectionEnd >= 0) {
return selectionEnd;
}
}
return ACCESSIBILITY_CURSOR_POSITION_UNDEFINED;
}
/**
* @hide
*/
@Override
public void setAccessibilitySelection(int start, int end) {
if (getAccessibilitySelectionStart() == start
&& getAccessibilitySelectionEnd() == end) {
return;
}
if (TextUtils.isEmpty(getContentDescription())) {
if (index >= 0 && index <= mText.length()) {
Selection.setSelection((Spannable) mText, index);
} else {
Selection.removeSelection((Spannable) mText);
}
CharSequence text = getIterableTextForAccessibility();
if (start >= 0 && start <= end && end <= text.length()) {
Selection.setSelection((Spannable) text, start, end);
} else {
super.setAccessibilityCursorPosition(index);
Selection.removeSelection((Spannable) text);
}
}