diff --git a/api/current.txt b/api/current.txt index 8df9c24010fc3..ab91974fb3b6f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -26352,21 +26352,28 @@ package android.view.accessibility { method public void setVisibleToUser(boolean); method public void writeToParcel(android.os.Parcel, int); field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40 + field public static final java.lang.String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN"; field public static final java.lang.String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING"; field public static final java.lang.String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT"; + field public static final java.lang.String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT"; + field public static final java.lang.String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT"; field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80 field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2 field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8 field public static final int ACTION_CLICK = 16; // 0x10 + field public static final int ACTION_COPY = 16384; // 0x4000 + field public static final int ACTION_CUT = 65536; // 0x10000 field public static final int ACTION_FOCUS = 1; // 0x1 field public static final int ACTION_LONG_CLICK = 32; // 0x20 field public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 256; // 0x100 field public static final int ACTION_NEXT_HTML_ELEMENT = 1024; // 0x400 + field public static final int ACTION_PASTE = 32768; // 0x8000 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 int ACTION_SET_SELECTION = 131072; // 0x20000 field public static final android.os.Parcelable.Creator CREATOR; field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2 field public static final int FOCUS_INPUT = 1; // 0x1 diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index b9babdcc2e039..11c80c26ebf19 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -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, diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 6d0a2375ece6c..7a3d7c3c14c7b 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -131,16 +131,22 @@ public class AccessibilityNodeInfo implements Parcelable { * at a given movement granularity. For example, move to the next character, * word, etc. *

- * Arguments: {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}
- * Example: + * Arguments: {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<, + * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}
+ * Example: Move to the previous character and do not extend selection. *

* 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); *

*

* + * @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. *

- * Arguments: {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}
- * Example: + * Arguments: {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<, + * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}
+ * Example: Move to the next character and do not extend selection. *

* 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); *

*

* + * @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. + *

+ * Arguments: {@link #ACTION_ARGUMENT_SELECTION_START_INT}, + * {@link #ACTION_ARGUMENT_SELECTION_END_INT}
+ * Example: + *

+ * 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); + *

+ *

+ * + * @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. *

@@ -226,9 +273,12 @@ public class AccessibilityNodeInfo implements Parcelable { * Actions: {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY}, * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY} *

+ * + * @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 { * Actions: {@link #ACTION_NEXT_HTML_ELEMENT}, * {@link #ACTION_PREVIOUS_HTML_ELEMENT} *

+ * + * @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. + *

+ * Type: boolean
+ * Actions: {@link #ACTION_NEXT_AT_MOVEMENT_GRANULARITY}, + * {@link #ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY} + *

+ * + * @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. + *

+ * Type: int
+ * Actions: {@link #ACTION_SET_SELECTION} + *

+ * + * @see #ACTION_SET_SELECTION + */ + public static final String ACTION_ARGUMENT_SELECTION_START_INT = + "ACTION_ARGUMENT_SELECTION_START_INT"; + + /** + * Argument for specifying the selection end. + *

+ * Type: int
+ * Actions: {@link #ACTION_SET_SELECTION} + *

+ * + * @see #ACTION_SET_SELECTION + */ + public static final String ACTION_ARGUMENT_SELECTION_END_INT = + "ACTION_ARGUMENT_SELECTION_END_INT"; /** * The input focus. diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index f8db622ab5c10..2f02780270f33 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -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); } } diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index 93187c1022ce9..f4592f78cf64d 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2225,7 +2225,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { | AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT | AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT | AccessibilityNodeInfo.ACTION_SCROLL_FORWARD - | AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD; + | AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD + | AccessibilityNodeInfo.ACTION_COPY + | AccessibilityNodeInfo.ACTION_PASTE + | AccessibilityNodeInfo.ACTION_CUT + | AccessibilityNodeInfo.ACTION_SET_SELECTION; private static final int RETRIEVAL_ALLOWING_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED